opencode2api: the management port binds wide, compose seeds config once, and there is no license file
A Go-based OpenCode Zen / Zen Go API proxy with OpenAI & Anthropic API compatibility, key/proxy pooling, automatic routing, and WebUI.
At a glance
- What is it?
- A Go gateway that translates three inference protocols and pools OpenCode Zen keys, with the WebUI compiled into the binary. Its defaults are documented carefully, and two of them reach further than you would expect on a default install.
- Who is it for?
- opencode2api is a small, readable codebase: one direct dependency, a standard library HTTP layer, three protocol translations and a WebUI that ships inside the executable, which is easy to fork and easy to audit. It is not a default install as it stands, because the example publishes a management interface that edits configuration and shows live logs, because compose treats your host config file as a one-time seed, and because the repository states no license at all.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 3, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The management port binds to every interface while the API binds to loopback
The example configuration listens on `127.0.0.1:8080` for the API and `0.0.0.0:8081` for the WebUI. That asymmetry is deliberate in one sense, since the API is the surface clients reach and the WebUI is the one you are told to restrict, but the WebUI is the surface that carries configuration editing, a Playground, diagnostics, token statistics and live logs. Three setup steps are listed before the first run: replace `server_keys` with your own local API key, supply Zen or Go keys or set `anonymous: true`, and replace `webui.password`, with the example enabling the WebUI under the username `admin`. In Docker the gap closes further, because the entrypoint rewrites the example API address `127.0.0.1:8080` to `0.0.0.0:8080` so published ports can reach it, and compose publishes both mappings. On a default compose install, both the inference gateway and the management console listen on every host interface.
Editing config.json on the host does nothing after the first compose startup
Compose imports the host configuration into the `opencode2api-state` volume only on first startup, and the documentation says subsequent changes should be made through the WebUI. Re-importing an edited file needs an explicit copy and a restart:
docker compose cp config.json opencode2api:/var/lib/opencode2api/config.json
docker compose restartThree different paths explain why. The compose file declares a top-level `configs` entry with `file: ./config.json` mounted at `/run/config/opencode2api.json`, which is the seed named by `CONFIG_SEED_PATH`. The live file is `CONFIG_PATH`, inside the `opencode2api-state` volume at `/var/lib/opencode2api/config.json`. And `read_only: true` means you cannot edit the live file in place either. So the host file is a template consumed once, and the practical consequence is that an edit made on the host can sit there looking correct while the service keeps running the copy it took at first boot.
Only the host side of a port mapping is configurable
The published mappings look adjustable and are not:
ports:
- "${OPENCODE2API_PORT:-8080}:8080"
- "${OPENCODE2API_WEBUI_PORT:-8081}:8081"Both environment variables set the left side only; the container side is written out as a literal. Moving the internal API port therefore touches at least three places: the listen address in `config.json`, the mapping in `compose.yaml`, and the image health check, which is fixed in the Dockerfile:
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -q -O /dev/null http://127.0.0.1:8080/healthz || exit 1A health check that points at a port nothing listens on marks the container unhealthy, so this is not cosmetic. `EXPOSE 8080 8081` is fixed for the same reason. On the version axis, `OPENCODE2API_VERSION` defaults to `latest` and the README advises pinning a release tag, while `pull_policy: always` means the image under whatever tag you pinned is re-fetched on every start. Pinning fixes the version number, not the bytes you get forever.
One direct dependency, and a module path with no domain in it
The dependency list is short enough to read in one glance: `go 1.24`, one direct requirement on `golang.org/x/crypto v0.41.0`, and one indirect entry on `golang.org/x/sys v0.35.0`. There is no HTTP framework, no SDK for any inference vendor and no configuration library in that list, so the three protocol translations, the SSE handling and the WebUI all sit on the standard library. That is worth knowing before you fork the translation layer, because there is very little between the wire format and your own edits. The module line reads `module opencode2api`, a bare word with no host in front of it, which means the project is built in place as a main package and cannot be fetched by another module by version. The Go floor agrees across all three places it appears: Go 1.24 or newer in the build instructions, `go 1.24` here, and a `golang:1.24-alpine` builder. The tree also carries no LICENSE file and the metadata records no license, so the terms you fork it under are not stated anywhere in the checkout.
The binary carries the WebUI, but the repository still wants Node for checks
Running the service requires no Node.js runtime or database, and the WebUI ships inside the executable. That claim covers serving, not working on the code. The repository keeps a `package.json` named `opencode2api-dev`, marked private, with one dev dependency and three scripts:
"format": "prettier --write .",
"format:check": "prettier --check .",
"check:web": "node --check webui/app.js"`prettier` is pinned at 3.9.6, with `.prettierrc.json` and `.prettierignore` alongside it, and a `package-lock.json` sits next to the manifest. `check:web` is a plain syntax check of the WebUI source through Node, so a contributor without Node installed cannot run the project's own check. The split is coherent: Go builds the thing that serves, Node formats and verifies the one JavaScript file the binary embeds. Just do not read the no-Node line as covering the whole repository.
The Dockerfile declares no USER, so the unprivileged run lives in the entrypoint
The claim that the container runs as an unprivileged user is true, but it is not enforced where you would look for it. The Dockerfile installs `ca-certificates su-exec tzdata`, creates a system group and a system user with `adduser -S -G opencode2api -h /var/lib/opencode2api opencode2api`, chowns `/app` and `/var/lib/opencode2api`, and copies `docker-entrypoint.sh` into place with mode 0755. It never issues a `USER` instruction. The drop to the unprivileged account is the entrypoint's job, performed with the `su-exec` it installs, and the entrypoint is also the component that rewrites the example API address at initialisation and owns the seed-versus-live config split. `STATE_DIR` is set to `/var/lib/opencode2api` in the same `ENV` block as `CONFIG_PATH`. One build detail worth knowing: `ARG VERSION=dev` feeds `-ldflags="-s -w -X main.version=${VERSION}"`, so a binary you build yourself reports `dev` unless you pass a version at build time.
The compatibility section stops inside its own heading
The last line of the English README opens a heading reading `### Compatibility bou` and goes no further, so the compatibility notes are not on the page and there is nothing to compare protocols against. What is written earlier still bounds what you can rely on: file content is carried only where the target protocol can represent it, responses come back as JSON or SSE across all three inference protocols, model discovery is dynamic with native protocol metadata and disk caches, and `GET /v1/models` returns only what the current configuration can actually route. Authentication is deliberately layered, with `server_keys` authenticating clients to the gateway, kept separate from `zen_keys` and `go_keys` and never used as upstream credentials. Health checks need no authentication, every response carries an `x-request-id`, and request bodies are capped at 32 MiB. Read those bounds before you pick it for a specific content type crossing protocols.
Editorial conclusion
opencode2api is a small, readable codebase: one direct dependency, a standard library HTTP layer, three protocol translations and a WebUI that ships inside the executable, which is easy to fork and easy to audit. It is not a default install as it stands, because the example publishes a management interface that edits configuration and shows live logs, because compose treats your host config file as a one-time seed, and because the repository states no license at all. Change the WebUI password and its bind address before the first run, decide whether you are editing configuration through the WebUI or on disk, and settle the licensing question with the maintainers before you vendor anything.
Frequently asked questions
Does opencode2api need Node.js or a database to run?
Not to serve traffic. The README states the executable includes the WebUI and that running the service requires no Node.js runtime or database. Node is still needed to work on the repository, since the `check:web` script runs `node --check webui/app.js` and prettier 3.9.6 is the single dev dependency.
Why did opencode2api ignore my edit to config.json after docker compose up?
Compose imports the host configuration into the `opencode2api-state` volume only on first startup, and the documentation says later changes belong in the WebUI. Re-importing requires `docker compose cp config.json opencode2api:/var/lib/opencode2api/config.json` followed by `docker compose restart`.
What credentials does opencode2api need before the first run?
Three things. Replace `server_keys` with your own local API key, supply Zen or Go keys or alternatively set `anonymous: true` and empty both upstream key arrays, and replace `webui.password`, since the example enables the WebUI under the username `admin`.
Which network interfaces does opencode2api listen on by default?
The example configuration uses `127.0.0.1:8080` for the API and `0.0.0.0:8081` for the WebUI. Under compose the entrypoint rewrites the API address to `0.0.0.0:8080` so published ports can reach it, and both mappings are published by default.
Can I run opencode2api without an OpenCode Zen or Go key?
The README describes optional anonymous Zen access using the OpenCode `public` credential: set `anonymous: true` and leave both upstream key arrays empty. It states no rate limit, quota or capability list for that path, so check what the anonymous tier actually serves before routing real work through it.
What license is opencode2api released under?
No license is recorded in the repository metadata, and the top-level entries contain no LICENSE file, so the terms are not stated anywhere in the checkout. There is also no CODE_OF_CONDUCT or SECURITY file at the root. Settle that with the maintainers before redistributing or vendoring the code.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jasonxu114514-opencode2api)