Back to Blog

OpenCode Still Says API Key Expired? Trace the Active Key

Tutorials and Guides6697
OpenCode Still Says API Key Expired? Trace the Active Key

You created a new API key, updated your settings, and launched OpenCode again. The same expiration message appears.

At this point, creating another key may add confusion. The next useful question is whether the process making the request actually received the replacement credential.

An OpenCode workflow can involve a terminal, a project configuration, saved provider credentials, a container, and a background server. Updating one location does not establish that every component now uses the same value.

This tutorial explains how to trace that connection, test a specific model route, and rotate credentials without losing track of which application uses them. It applies to direct providers and compatible AI API gateways, including setups that access DeepSeek or GPT through a custom provider.

If you have not created a replacement credential yet, begin with the OpenCode API key expiration guide. The workflow below addresses the harder follow-up: a replacement exists, but requests still fail.

1. Identify the service that rejected the credential

Start with the failing request's destination.

An OpenCode Zen credential belongs to its issuing service. A gateway credential belongs to the gateway. A direct model-provider credential belongs to that provider. These credentials are not interchangeable simply because their services expose similar API formats.

Write down four non-secret values:

For example, a configuration that calls a gateway must use a credential issued for that gateway. Replacing an OpenCode Zen key will not repair a request that still targets another service.

Also check where the error originated. A client can display a message returned by a remote service, while a gateway may relay an upstream failure. Preserve a sanitized request ID or timestamp when available so you can investigate the correct layer.

Do not infer the failing account solely from the model name. The same model family can be accessible through different commercial services, each with its own authentication and account permissions.

2. Build a credential-source inventory

Before editing configuration, list the places that could supply credentials or select a provider.

LocationWhat to check
Saved provider credentialsWhether the replacement was stored under the intended provider ID
Global configurationWhether it defines a provider or explicit API key
Project configurationWhether it changes the provider, model, or credential reference
Custom configuration fileWhether the launcher selects an unexpected file
Environment variablesWhether the running process received the current secret
Container deploymentWhether the container was recreated with updated values
Background serverWhether requests are executed by an older process
CI secret storeWhether the workflow references the intended secret and environment

This inventory is a troubleshooting tool, not a universal precedence table.

OpenCode's configuration documentation explains that configuration sources are merged and that later sources can override conflicting settings. A custom configuration file does not necessarily eliminate project or runtime overrides. It also documents environment substitution through {env:VARIABLE_NAME}.

Credential selection adds another layer. Inspect the configuration relevant to the selected provider instead of assuming that changing one global variable changes every model connection.

A useful incident note might say:

> The interactive terminal receives credential version B. The container was created with version A. Both select the same gateway model.

That statement gives you a specific deployment problem to fix. “The new key does not work” leaves too many possibilities open.

3. Make the intended provider connection explicit

For a controlled test, use a clearly named custom provider and one documented model route.

The example below uses an OpenAI-compatible Chat Completions adapter. Replace both occurrences of YOUR_CONFIRMED_MODEL_ID with the exact identifier available to your account.

json
{
  "$schema": "https://opencode.ai/config.json",
  "model": "4sapi/YOUR_CONFIRMED_MODEL_ID",
  "provider": {
    "4sapi": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "4SAPI test connection",
      "options": {
        "baseURL": "https://4sapi.com/v1",
        "apiKey": "{env:FOURSAPI_API_KEY}"
      },
      "models": {
        "YOUR_CONFIRMED_MODEL_ID": {
          "name": "Verified test model"
        }
      }
    }
  }
}

FOURSAPI_API_KEY is a variable name chosen for this tutorial. It becomes relevant because the configuration explicitly references it.

The official custom-provider documentation distinguishes the adapter used for Chat Completions from the adapter used for Responses. Confirm which interface your selected model requires. A valid credential cannot repair an incompatible protocol.

For a DeepSeek route documented to support this adapter, use its confirmed gateway identifier. For a GPT route requiring Responses, follow the corresponding provider configuration instead of copying this Chat Completions example unchanged.

4. Check the inherited environment without printing the key

A frequent source of confusion is updating a secret store while testing from a terminal that still holds an older value.

The following local script compares the environment variable available to Python with a replacement entered through a hidden prompt.

Save it as check_key_environment.py.

python
import getpass
import hmac
import os

active = os.environ.get("FOURSAPI_API_KEY")

if not active:
    raise SystemExit(
        "FOURSAPI_API_KEY is missing from this process environment."
    )

candidate = getpass.getpass(
    "Paste the intended replacement key for a local comparison: "
)

if not candidate:
    raise SystemExit("No comparison value supplied.")

matches = hmac.compare_digest(
    active.encode("utf-8"),
    candidate.encode("utf-8"),
)

print("Environment matches replacement:", matches)
print("Environment contains surrounding whitespace:", active != active.strip())

Run it from the same terminal that launches the application:

bash
python check_key_environment.py

The script makes no network request and does not print either secret.

Its scope matters: it checks the environment inherited by this Python process. It does not inspect an already running OpenCode server, prove which configuration overrides apply, or establish that the service accepts the key.

If the comparison fails, correct the secret injection method. If it succeeds, continue to provider selection and request verification.

Avoid dumping all environment variables or complete configuration files into shared logs. Those outputs can contain unrelated credentials as well as the key you are investigating.

5. Refresh the process that actually sends requests

Identify the component responsible for network calls.

When OpenCode runs directly in your terminal, starting a new process from the corrected environment may be sufficient. When a client attaches to a background server, the server's environment and configuration matter.

Restarting the visible client does not necessarily replace the backend.

Containers introduce another distinction. Docker documents that docker compose restart does not apply changed container environment settings. A restart can therefore reproduce the same failure even after you edit a configuration file.

Use your deployment's normal update procedure. For a Compose service actually named opencode, a targeted recreation can look like:

bash
docker compose up -d --no-deps --force-recreate opencode

Replace the service name with your own. Check persistent storage and active work before recreating a service.

Also inspect how Compose receives the variable. An environment entry, an env_file, and a shell-provided value can interact. Docker's environment precedence documentation describes those rules.

The practical objective is simple: establish which secret version the new process receives, then verify a request from that process.

6. Test the exact provider and model

Do not begin with a large coding task.

First select one known model route and ask for a short response. The OpenCode CLI reference documents explicit model selection in provider/model form.

After replacing the model placeholder, run:

bash
opencode run \
  --model "4sapi/YOUR_CONFIRMED_MODEL_ID" \
  "Reply with the word READY. Do not use tools."

Use a clean test directory and the intended configuration. Check project-level settings before assuming the command selects a fully isolated setup.

This request may be billable. It is a small authentication and text-generation check, not a performance benchmark.

A model appearing in a selection list is not equivalent to a successful inference request. Likewise, a successful short answer does not establish that tools, streaming, or long contexts work.

After the basic request succeeds, test one small example of the capability your application actually needs. Keep that second check separate so you know which stage introduced a failure.

7. Interpret the next error before changing anything else

A replacement credential can resolve authentication and reveal a different problem.

Observed resultNext investigation
Expiration message persistsActive credential source, issuing service, and remote request details
Invalid credential responseKey value, destination, and account status
Model unavailable responseExact model ID and account access
Unsupported parameter responseSelected adapter and request schema
Rate-limit responseRequest volume, quota, and service-specific limits
Connection failureNetwork path, proxy settings, and destination
Text works but tools failTool protocol and model capability

These categories guide investigation; they are not universal interpretations of every HTTP status.

Read the service's error body. A gateway can add its own routing or account error, and different providers may use the same status code for different conditions.

Keep one variable fixed while changing another. If you rotate the key, switch models, change the endpoint, and update the client together, a successful result will not tell you which action mattered.

8. Make future rotations easier to verify

Once the connection works, record the ownership of the credential.

A small team can maintain a private inventory containing:

Store references and labels rather than raw key values.

For an ordinary planned rotation, provision the replacement, update a limited deployment, verify it, and then migrate the remaining consumers. Retire the previous credential according to the issuer's capabilities and your operational policy.

A suspected compromise requires a different response: prompt revocation may take priority over a gradual transition.

An API relay can simplify where your application connects, but it still needs disciplined credential management. A shared endpoint does not automatically synchronize secrets across terminals, containers, and CI jobs.

Frequently asked questions

Why does the new key work in a script but fail in OpenCode?

The two processes may use different destinations, model IDs, credentials, or configuration sources. Compare those values first. A successful standalone script only validates the connection that script actually used.

Can I fix this by signing in again?

Follow the issuing service's instructions for the credential involved. Refreshing an interactive account session does not establish that an independently stored API key has been replaced in your application.

Should I delete every saved credential?

Start with the affected provider. Removing unrelated credentials creates additional work and can disrupt functioning connections. Use a controlled test to identify the stale source before changing other integrations.

Does changing the API gateway guarantee the error disappears?

No. A gateway introduces its own endpoint, credential, model permissions, and protocol requirements. Configure and verify that combination explicitly.

Verify one working connection

To evaluate a gateway connection, open the 4SAPI login, register or sign in, and check the model route you want to use.

Create a dedicated test credential, supply it through your chosen secret mechanism, and complete the short model request above. Your first milestone is a verified connection with a known credential source.

Tags:opencode api key expiredopencode new api key still expiredopencode environment variable overrideopencode custom provider api keyAI API gateway

Recommended reading

Explore more frontier insights and industry know-how.