On this page

Start with the guidance below for OpenCode errors such as "the free tier can only be used from within OpenCode," "OpenCode 1.18.0 or newer is required," and HTTP 403. If you choose DigitalOcean's paid inference route, the disposable read-only test then helps isolate key, endpoint, model, account and tool-permission failures without using a real project.

Review history and execution status

  • 11 September 2026: the original DigitalOcean recipe combined documented custom-provider fields and the Chat Completions endpoint; it did not record a successful paid run.
  • 4 October 2026: the English article added the existing-provider and free-tier checks before the optional paid route.
  • 8 October 2026: a targeted documentation review checked CLI/model troubleshooting, Zen terms, provider configuration, permissions and DigitalOcean’s Chat Completions/billing references. The French opening now follows the same provider-first path. This is a documentation check, not a new execution result.

The paid recipe below targets the OpenCode 1.x CLI (opencode-ai) and its documented permission configuration. No exact OpenCode patch, model or operating-system combination has been execution-verified for this article, so there is no tested version pin. Record your actual opencode --version output before using the acceptance template after step 7. OpenCode 2 compatibility is outside this recipe’s checked scope.

Review sources: CLI and upgrades · Models · Troubleshooting · Zen terms · Custom providers · Permissions · Chat Completions · Inference billing.

Match the error before choosing a provider

Start with the exact message and the provider/model shown in OpenCode. You do not need to buy inference to investigate a version or provider-selection error.

  • "OpenCode 1.18.0 or newer is required", or another minimum-version message: run opencode --version, then use the documented opencode upgrade command and restart the CLI. Check the version again in the terminal you actually use. Follow the minimum in your current error rather than treating 1.18.0 as a permanent requirement. Updating the CLI does not add credits or change account access. See the official upgrade reference.
  • "The free tier can only be used from within OpenCode": if this appears in another client or a direct API request, retry through OpenCode itself. In OpenCode, use /models to check that you selected the intended OpenCode Zen model rather than a custom endpoint or another provider. Check the current Zen pricing and free-model terms before sending a prompt. Free offerings can be temporary; this guide does not promise continued access, unlimited usage or eligibility. If the message persists in an updated OpenCode client, retain the redacted error and ask upstream for help instead of changing headers to impersonate a client.
  • ProviderModelNotFoundError or an unexpected provider/model: run opencode models and compare your configured providerId/modelId with the available entries. Use /models to select the intended entry. If authentication is needed, use /connect for that provider and check its access requirements. The official model troubleshooting and model-selection guide explain these checks.
  • HTTP 401 or 403: record which provider returned it. A status code alone does not distinguish an invalid credential, account restriction or a free-tier policy. Check that provider's authentication and access rules. For DigitalOcean, use the account/key checks below only if you already intend to use its paid inference service.

The quoted version and free-tier messages are examples, not universal errors or guaranteed fixes. If a currently available model under your existing provider meets your needs, keep that route. DigitalOcean Serverless Inference is an optional paid route for readers who want its supported models and accept its billing and access requirements. The disposable recipe below specifically tests that route; it does not unlock OpenCode's free tier.

1. Recognize the failure

Use this guide when an attempted OpenCode DigitalOcean setup cannot authenticate, find a model, reach the provider, or complete a tool action. There is no single error string to match. A provider error may originate in the key, base URL, model ID, account access or requested tool, before the model can do useful work.

This is a diagnostic path for an already installed OpenCode CLI on macOS or Linux, using Bash and Python 3.9 or newer. Windows readers can follow it inside WSL with those tools installed there. Check opencode --version and python3 --version first. Return to the setup guide if installation is incomplete.

The configuration below combines OpenCode custom-provider fields with DigitalOcean’s Chat Completions endpoint. Follow the review and version scope above. No exact provider error, model response or successful run is promised.

2. Keep the test harmless

Open a fresh terminal and enter bash to use the shell syntax below. Do not run this test inside a working repository. Create a disposable directory with the block below, then keep this terminal open. It contains one harmless text fixture; you will add only a credential-free configuration file.

Write down the printed directory and the two checksum numbers. The file should contain exactly one line: The demo garden has 7 blue pots. Do not copy secrets, a real repository, .env files or custom plugins into this folder. Never place credentials in committed files.

This limits agent tool actions; it is not an operating-system sandbox. OpenCode configuration is merged from multiple locations. Before launch, inspect your global configuration, installed plugins, agent overrides and OPENCODE_CONFIG, OPENCODE_CONFIG_DIR and OPENCODE_CONFIG_CONTENT settings. Use a clean local OS account if you cannot account for inherited configuration. Ask your administrator about managed settings instead of trying to bypass them.

Shell command
TEST_DIR=$(mktemp -d "${TMPDIR:-/tmp}/opencode-provider-test.XXXXXX")
cd "$TEST_DIR" || exit 1
printf 'The demo garden has 7 blue pots.\n' > fixture.txt
pwd
cksum fixture.txt

How OpenCode merges configuration

3. Check the key and available models

Local setup and checksums need no cloud account. The following network checks require a DigitalOcean account with Serverless Inference access, a model access key and eligible promotional credits or prepaid funds. Inference is billed. Check the current limits and your account tier before sending prompts; tiers 1 and 2 restrict commercial model access. Do not enable automatic balance reload just for this test.

Use a model access key intended for this small test, entered at the hidden terminal prompt below. The environment variable name DO_INFERENCE_API_KEY is our local convention, not a required DigitalOcean name. The presence check prints no secret and does not establish validity. Stop if it says the key is missing.

A VPC-restricted key cannot authenticate from an ordinary laptop outside that VPC. Use an approved test key scoped to your chosen model with network access appropriate to this local test. Do not weaken a production key. The key-management reference explains creation under INFERENCE → Manage and model/VPC restrictions.

Then list model IDs using the documented GET /v1/models endpoint and Bearer authentication. Choose a text model that supports Chat Completions and tool calls according to the current catalog. DigitalOcean documents llama3.3-70b-instruct in its Chat Completions examples, but do not assume that example is available to your account. Copy a current ID rather than a display label or a router name.

Shell command
set +x
read -r -s -p 'Model access key: ' DO_INFERENCE_API_KEY
printf '\n'
export DO_INFERENCE_API_KEY
if [ -n "${DO_INFERENCE_API_KEY:-}" ]; then
  printf 'Key is set; value hidden.\n'
else
  printf 'Key is missing. Stop here.\n'
fi
Shell command
python3 - <<'PYTHON'
import json, os, urllib.request, urllib.error
key = os.environ.get("DO_INFERENCE_API_KEY")
if not key:
    raise SystemExit("Missing key")
request = urllib.request.Request(
    "https://inference.do-ai.run/v1/models",
    headers={"Authorization": "Bearer " + key},
)
try:
    with urllib.request.urlopen(request, timeout=30) as response:
        result = json.load(response)
    for model in result["data"]:
        print(model["id"])
except urllib.error.HTTPError as error:
    raise SystemExit(f"HTTP {error.code}: check account, key and endpoint")
except urllib.error.URLError:
    raise SystemExit("Network/TLS failure: check connection and trusted certificates")
PYTHON

Account and model limits · Documented Chat Completions examples · Current model catalog · Create and manage model access keys

4. Configure one provider and deny agent writes

In your editor, save the JSON below as opencode.json inside the printed disposable directory. Replace all three MODEL_ID occurrences with the same verified Chat Completions model ID. Do not substitute the actual secret for {env:DO_INFERENCE_API_KEY}.

do-readonly is an arbitrary local provider identifier. The model and small_model fields select provider/model so auxiliary model selection uses the same provider. The models map key is the API model ID; name is only a display label. options.apiKey reads the environment variable. options.baseURL ends in /v1, not /chat/completions: the @ai-sdk/openai-compatible adapter supplies the Chat Completions route.

This explicit diagnostic provider is separate from the built-in DigitalOcean /connect flow, which also supports OAuth. Keep your existing setup intact. A Responses-only model needs a different adapter; do not force it through this Chat Completions example.

Most OpenCode permissions default to allow. Here the catch-all denies tools, and read allows only the fixture. edit controls writes and patches too; bash and external-directory access are denied explicitly. The Build agent repeats these restrictions because agent rules take precedence. Stay on Build throughout the test.

opencode.json
{
  "$schema": "https://opencode.ai/config.json",
  "default_agent": "build",
  "model": "do-readonly/MODEL_ID",
  "small_model": "do-readonly/MODEL_ID",
  "share": "disabled",
  "provider": {
    "do-readonly": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "DigitalOcean read-only diagnostic",
      "options": {
        "baseURL": "https://inference.do-ai.run/v1",
        "apiKey": "{env:DO_INFERENCE_API_KEY}"
      },
      "models": {
        "MODEL_ID": {
          "name": "Selected chat model"
        }
      }
    }
  },
  "permission": {
    "*": "deny",
    "read": {
      "*": "deny",
      "fixture.txt": "allow",
      "*/fixture.txt": "allow"
    },
    "edit": "deny",
    "bash": "deny",
    "external_directory": "deny"
  },
  "agent": {
    "build": {
      "permission": {
        "*": "deny",
        "read": {
          "*": "deny",
          "fixture.txt": "allow",
          "*/fixture.txt": "allow"
        },
        "edit": "deny",
        "bash": "deny",
        "external_directory": "deny"
      }
    }
  }
}

Custom-provider fields · Permission defaults and agent precedence

5. Run one read-only request

Run opencode in the same Bash terminal. Check that Build and do-readonly/your-selected-model are active; use /models to choose that exact entry if needed. Paste the narrow prompt below. Do not attach the fixture with @ or paste its contents into the prompt, because the test should exercise the read tool.

Success means the session shows a read of fixture.txt and the answer identifies seven blue pots in a demo garden. Exit OpenCode, run cksum fixture.txt yourself, and compare both numbers with your baseline. Inspect the directory for unexpected files. An answer without a file-read event does not pass the read-tool check. A refusal to read points to permissions or tool compatibility, not automatically a bad key.

Only after this succeeds, consider the referral link below if you need a separate account for future tests. I may earn a commission through it. It opens the DigitalOcean referral destination; you still create a key in the Control Panel. It does not configure OpenCode or guarantee eligibility, credits or access.

Shell command
opencode
TEXT
Use the read tool to inspect fixture.txt only. Summarize its single fact in one sentence. Do not edit or create files, run commands, or access another path.

6. If the request fails, isolate the failing layer

Authentication: an HTTP 401 or 403 is a clue, not an exact diagnosis. Check the intended account, key validity and scope, and the same-terminal environment check. If a key is revoked or exposed, replace it through the Control Panel. Do not print it, put it in JSON or repeatedly generate new keys to fix an unrelated endpoint problem.

Endpoint: compare the base URL character for character with https://inference.do-ai.run/v1. A duplicated /v1 or appended /chat/completions can target the wrong route. Do not substitute the control-plane api.digitalocean.com URL or an Agent Platform endpoint.

Model: compare all three configured IDs with the model-list result and current model capabilities. A list entry is not proof of tool support or account entitlement. Account/network: check the prepaid balance, tier, rate limits, DNS, proxy and trusted TLS certificates. For throttling, pause and respect any retry guidance. Do not disable certificate verification.

If the list succeeds but OpenCode fails, run the optional direct chat probe below after saving the configuration. This makes one small billed request without tools or repository content. It uses the documented max_completion_tokens limit; max_tokens is deprecated. Chat response received: True narrows the fault to the OpenCode path or advanced model features; it does not validate streaming or tool calls. An HTTP failure here keeps the investigation at the provider/account/API layer.

If direct chat succeeds, confirm the loaded file and provider, inspect overrides, then follow the official OpenCode troubleshooting instructions for your installed version. Record the version, model ID, HTTP status and a redacted error. Logs can contain prompts or credentials: review them before sharing. Do not delete your entire configuration or credential store as a first step.

Shell command
python3 - <<'PYTHON'
import json, os, urllib.request, urllib.error
with open("opencode.json", encoding="utf-8") as file:
    config = json.load(file)
model = config["model"].removeprefix("do-readonly/")
if model == "MODEL_ID":
    raise SystemExit("Replace MODEL_ID first")
key = os.environ.get("DO_INFERENCE_API_KEY")
if not key:
    raise SystemExit("Missing key")
payload = {"model": model, "messages": [{"role": "user", "content": "Reply with OK."}], "max_completion_tokens": 32}
request = urllib.request.Request(
    "https://inference.do-ai.run/v1/chat/completions",
    data=json.dumps(payload).encode(),
    headers={"Authorization": "Bearer " + key, "Content-Type": "application/json"},
)
try:
    with urllib.request.urlopen(request, timeout=60) as response:
        result = json.load(response)
    print("Chat response received:", bool(result.get("choices")))
except urllib.error.HTTPError as error:
    raise SystemExit(f"HTTP {error.code}: consult the troubleshooting branches")
except urllib.error.URLError:
    raise SystemExit("Network/TLS failure")
PYTHON

Official OpenCode troubleshooting · Supported inference endpoints

7. Prove the denial before allowing a first edit

Restart OpenCode with the same configuration and paste the denial prompt below. Expected result: the write is unavailable or blocked, and fixture.txt retains its original checksum after you exit. Inspect the tool record as well as the file; a sentence saying “I cannot” alone does not prove enforcement.

If the UI shows no attempted or unavailable tool evidence, the denial check is inconclusive. After the successful read-only test, close OpenCode and deliberately change edit from deny to ask in both permission and agent.build.permission. Keep bash and every other denial unchanged. Leave automatic approval off; do not start with --auto, because it approves actions that would otherwise ask. Restart and repeat the write prompt. Reject the actual edit approval. The rejected tool event plus an unchanged checksum is the observable denial check. If no approval appears, stop and investigate rather than calling this a pass.

If any write executes without approval or either checksum number changes, stop. Do not open a real repository. Correct the configuration and repeat from a fresh fixture. To make a first intentional edit later, keep edit set to ask, review the proposed fixture-only change and approve once. Avoid always, which grants broader permission for the rest of the session. Restore edit to deny in both locations when you finish.

TEXT
Try to change fixture.txt so that blue becomes red, using the edit tool. Do not use shell commands or another tool to bypass a denial. Report the tool result.
Shell command
cksum fixture.txt

Acceptance record template, not an executed result

No paid request or live read/denial event was recorded for this review. Complete this record only from your own run; leave unobserved items as “not run” or “inconclusive.” Redact credentials, account identifiers and unrelated paths before sharing.

TEXT
Status: NOT RUN
Run date and time: not recorded
OS, Bash and Python versions: not recorded
Exact opencode --version output: not recorded
Provider/model ID: not recorded
Inherited configuration reviewed: not recorded
fixture.txt checksum before (both numbers): not recorded
Observed read event and summarized fixture fact: not recorded
Denied/unavailable edit event, or rejected ask approval: not recorded
Automatic approval confirmed off for the ask check: not recorded
fixture.txt checksum after (both numbers): not recorded
Unexpected files or shell actions: not checked
Optional billed direct-chat probe: not run
Redacted error/HTTP status, if any: not recorded
Overall result (pass/fail/inconclusive): not run

A pass needs both the real read event and an observable blocked or rejected edit, unchanged checksums and no unexpected agent writes or commands. A prose refusal alone is inconclusive. Record a partial success separately; a working chat endpoint does not pass the tool-permission test.

8. Return to a real repository carefully, then clean up

Carry over only the provider settings you verified and permissions you understand. Keep the successful test record: OpenCode version, model ID, fixture checksum, read event and blocked or rejected edit event. Do not include the key.

For cleanup, exit OpenCode and unset DO_INFERENCE_API_KEY in the test terminal. Use your file manager to move only the exact opencode-provider-test directory printed earlier to Trash. Do not delete a parent directory. OpenCode session data and caches can remain outside that folder; consult its documentation if you also want to remove those. Revoke a temporary model access key in the Control Panel when it is no longer needed.

  • Start on a branch or isolated worktree with a known clean baseline.
  • Review proposed diffs before accepting or committing changes.
  • Retain approval for writes and shell actions; allow only the commands you understand.
  • Never paste production secrets into prompts. Check what context will be sent to the hosted model.

Initial OpenCode setup · Debug a failing build with AI

Frequently asked questions

Why does OpenCode report an invalid key?

First check that the variable is nonempty in the terminal launching OpenCode. That does not validate the key. Use a Serverless Inference model access key for the intended account, check expiry or revocation, and retry the model-list request. Never paste the key into a prompt or bug report.

What if the model is unsupported?

Copy the exact model ID from the current model list and check its Chat Completions and tool-calling support in the official catalog. Replace all three MODEL_ID occurrences. A model appearing in the list does not prove your account can complete every kind of request.

Does this test change files?

You create fixture.txt and opencode.json yourself. The agent must leave the fixture unchanged and must not create project files or run shell commands. OpenCode can still save its own session data and caches. Compare the fixture checksum and inspect the folder after each test.

What if the endpoint works but OpenCode still fails?

A successful direct chat probe isolates basic authentication and model access. Check the selected do-readonly provider, the loaded configuration, agent overrides and OpenCode version. A plain chat response does not prove tool-calling compatibility. Share only a redacted error and version when asking for help.