qwen2API: turning the Qwen web session into OpenAI, Anthropic and Gemini endpoints
A project converting the Qwen web page to an API.
At a glance
- What is it?
- qwen2API is a self-hosted Go gateway that exposes a Qwen web account through three API dialects at once. It is a good fit if you already hold Qwen web credentials and want SDK compatibility, and the wrong tool if you need a vendor-supported inference contract.
- Who is it for?
- Adopt qwen2API if you already hold Qwen web accounts, want OpenAI, Anthropic or Gemini SDK compatibility without changing client code, and can accept that the upstream is a web protocol rather than a documented inference API. Do not adopt it if you need a vendor SLA, a published rate limit, or a licence you can read before shipping: the README badge says GPL-3.0 but the repository has no LICENSE file in its top-level entries.
- 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 94 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 September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What qwen2API actually converts, and for whom
Qwen's web product is a browser session. There is no SDK that speaks to it, no streaming contract you can rely on in code, and no way to point an existing OpenAI client at it. qwen2API sits in that gap: it is a gateway that converts Qwen Web capabilities into common API protocols, and ships a local WebUI for account management, downstream API keys, runtime settings, and chat, image and video tests.
The audience is narrow and specific. You need to already have a Qwen account token, because the gateway does not create accounts for you. You need to be comfortable running a container that holds those credentials on disk. In exchange, the same process answers on `/v1/chat/completions`, `/v1/messages` and `/v1beta/models/{model}:generateContent`, so a Python OpenAI client, a Claude-style client and a Gemini-style client can all point at one host. The README also lists `/v1/responses`, `/v1/models`, `/v1/files`, `/v1/images/generations` and `/v1/videos/generations` on the OpenAI side, plus `/anthropic/v1/messages` and `/v1/messages/count_tokens` on the Anthropic side.
That breadth is the point. Most projects in this space pick one dialect. qwen2API picks three and puts a React WebUI in front of them, which means the person operating it does not have to edit JSON files by hand to add an account or rotate a key.
The request path: router, adapters, tool pipeline, account pool
The README's architecture diagram describes a linear flow. Clients arrive at a Go HTTP router. The router hands the request to protocol adapters, which normalize the incoming shape (OpenAI, Anthropic or Gemini) into whatever the internal tool-call and context pipeline expects. That pipeline then reaches into the Qwen account pool, which selects an upstream account and talks to Qwen Web. Persistence is deliberately unglamorous: JSON stores under the data volume, logs under the logs volume.
The account pool is where most of the operational behaviour lives. It supports multi-account rotation, per-account concurrency, and separate cooldown tracking for chat, image and video. That last detail matters more than it sounds. If image generation gets rate limited, the chat path should not be punished for it, and the pool is built so it is not. Concurrency is bounded by `MAX_INFLIGHT_PER_ACCOUNT`, which the example environment file sets to 2.
Two mechanisms hint at how fragile the upstream is. First, `TOOL_RECOVERY_MAX_ATTEMPTS` exists because an upstream response after a tool result may fail to produce the next client tool call; the gateway retries automatically, defaulting to 4 and clamped between 1 and 8. Second, the environment file carries a cluster of stream timeouts (`UPSTREAM_STREAM_HEADER_TIMEOUT_SECONDS`, `UPSTREAM_STREAM_FIRST_EVENT_TIMEOUT_SECONDS`, `UPSTREAM_STREAM_IDLE_TIMEOUT_SECONDS`) and a prewarm cache for chat IDs (`CHAT_ID_PREWARM_TARGET_PER_ACCOUNT`, `CHAT_ID_PREWARM_TTL_SECONDS`). You do not build a chat-ID prewarm cache for a stable API. You build it for a web endpoint that expects a session to exist before you speak to it.
The Dockerfile confirms the same story from another angle. The runtime image installs a long list of X11, GTK, NSS and font packages, and sets `PLAYWRIGHT_BROWSERS_PATH=/ms-playwright`. This is not a thin HTTP proxy; the image is provisioned to run browser automation. That is the real cost of converting a web page into an API, and it is why the image is heavier than the Go binary alone would suggest.
Installing qwen2API with Docker Compose and reaching the WebUI
The README recommends the Docker Hub image for most deployments. Create a working directory with `data` and `logs` beside the compose file, so upgrades do not wipe accounts, keys or logs.
mkdir qwen2api
cd qwen2api
mkdir -p data logsThen create a `.env` next to the compose file. `ADMIN_KEY` guards the WebUI and the `/api/admin/*` routes, so it should be a strong private value rather than the placeholder.
HOST_PORT=7860
HOST_DATA_DIR=./data
HOST_LOGS_DIR=./logs
ADMIN_KEY=replace-with-your-own-strong-random-keyThe compose file maps the host port to the container's `PORT` (default 7860), mounts the two directories into `/app/data` and `/app/logs`, and defines a healthcheck against `/healthz` with a 120 second start period. The README is explicit that you do not need to set paths for `accounts.json` or `api_keys.json`: the image already uses `/app/data` and `/app/logs`, and the volume mapping decides where those files live on the host.
services:
qwen2api:
image: ${QWEN2API_IMAGE:-yujunzhixue/qwen2api:latest}
container_name: qwen2api
restart: unless-stopped
init: true
env_file:
- .env
ports:
- "${HOST_PORT:-7860}:${PORT:-7860}"
volumes:
- ${HOST_DATA_DIR:-./data}:/app/data
- ${HOST_LOGS_DIR:-./logs}:/app/logsStart it and follow the logs. The first start is the slow one, because of the browser dependencies in the image and the 120 second healthcheck grace period.
docker compose pull
docker compose up -d
docker compose logs -f qwen2apiWhen the container reports healthy, open `http://127.0.0.1:7860` for the WebUI, `http://127.0.0.1:7860/healthz` for the health check, and `http://127.0.0.1:7860/keepalive` for the keepalive probe. Add a Qwen account in the WebUI, then create a downstream API key and point an OpenAI-compatible client at `http://127.0.0.1:7860/v1`. If you changed the source and need your own image, the README's build path clones the repository, copies `.env.example` to `.env`, and builds with `docker compose -f docker-compose.yml -f docker-compose.build.yml build`.
Environment keys that are runtime-only, and why that surprises people
The `.env.example` file draws a line that is easy to miss. `QWEN_API_KEY`, `QWEN_API_KEYS` and `QWEN_API_KEY_N` inject downstream API keys from the environment, but they are not written to `data/api_keys.json` and cannot be deleted from the WebUI. The same applies to `QWEN_ACCOUNT_N`, which takes the format `token;optional-email;optional-password` and is not written to `data/accounts.json`.
That is a sensible security choice and an operational trap. If you inject accounts through the environment and then wonder why the WebUI account list looks empty, the answer is that the two stores are separate. Removing an environment-injected key means editing the environment and restarting, not clicking delete. Teams that mix both sources will eventually confuse the two.
The keepalive settings follow the same pattern: `KEEPALIVE_URL` and `KEEPALIVE_INTERVAL` lock the equivalent WebUI settings when set in the environment. If you want to tune those from the UI, leave them out of the env file.
A second cluster of variables is left at defaults that most operators will need to revisit. `BROWSER_POOL_SIZE=1`, `WORKERS=1` and `MAX_INFLIGHT_PER_ACCOUNT=2` are conservative. `ACCOUNT_MIN_INTERVAL_MS`, `REQUEST_JITTER_MIN_MS` and `REQUEST_JITTER_MAX_MS` all default to 0, meaning no pacing and no jitter out of the box. Given that the upstream is a web session with its own rate limiting, running with zero jitter and a single browser is a choice you should make deliberately rather than by omission.
Where qwen2API breaks: account bans, protocol drift and the licence question
The honest limitation is upstream. This project converts a web page, and web pages change without notice. The presence of `TOOL_RECOVERY_MAX_ATTEMPTS`, the stream header, first-event and idle timeouts, and the chat-ID prewarm cache all point to an upstream that does not behave like a documented inference API. When Qwen changes its web protocol, the gateway breaks until the maintainers adapt it. There is no release retrieved for this repository, so there is no changelog to read for a compatibility promise.
Account risk is the second limitation. The pool rotates accounts and tracks cooldowns per capability, with `RATE_LIMIT_BASE_COOLDOWN` at 600 seconds and `RATE_LIMIT_MAX_COOLDOWN` at 3600. Those are mitigations, not guarantees. If the upstream decides an account is abusive, the gateway can rotate away from it but cannot appeal. Anyone planning to run production traffic through a personal Qwen account should treat that account as expendable.
The third issue is licensing. The README badge says GPL-3.0, but the repository's top-level entries do not include a LICENSE file. The badge is a signal, not the licence text, and I would not treat a badge as the grant. If you intend to redistribute the image or embed the gateway in a product, resolve that discrepancy before you ship rather than after.
Finally, this is the wrong tool if you need a vendor-supported contract. qwen2API gives you SDK compatibility, not an SLA. If your application depends on documented rate limits, a support channel and a deprecation policy, use a hosted inference API instead and accept the cost.
qwen2API versus LiteLLM: adapter breadth against upstream stability
The closest comparison is LiteLLM. Both give you an OpenAI-compatible surface in front of other models, and both can be self-hosted. The difference is where the conversion happens.
LiteLLM translates between provider APIs that are already documented and versioned. Its job is normalization: take twenty real APIs and present one shape. qwen2API translates between an undocumented web session and three API dialects. Its job is emulation, including the browser automation the Dockerfile provisions for and the tool-call recovery loop that exists because the upstream sometimes fails to continue a tool sequence.
That changes what you are buying. With LiteLLM, an upstream change usually means a version bump. With qwen2API, an upstream change means the gateway's assumptions about the web protocol are wrong and someone has to reverse-engineer the new behaviour. The trade is real: qwen2API covers Anthropic and Gemini dialects as well as OpenAI, and it manages a pool of web accounts with per-capability cooldowns, which is not what LiteLLM is built for. If your goal is to use Qwen web credentials you already have, LiteLLM does not solve that problem at all.
A second difference is deployment weight. LiteLLM is a Python service you can run almost anywhere. qwen2API's runtime image carries a full set of browser libraries, and `BROWSER_POOL_SIZE` controls how many browser instances are kept. That is the price of the emulation layer, and it shows up in image size, memory and startup time.
Maintenance, upgrades and what the repository tells you
The last push to the default branch was on 2026-06-13. The repository is not archived. There are no releases retrieved, so there is no version history to consult and no migration notes to follow.
Upgrading is straightforward in mechanism and risky in practice. The compose file pins `yujunzhixue/qwen2api:latest` by default, which means `docker compose pull` will move you to whatever was published most recently. Because state lives in the mounted `data` and `logs` directories rather than in the container, replacing the container preserves accounts and keys. The README states this directly: keeping `data` and `logs` beside the compose file means upgrades do not wipe your accounts, keys or logs.
What is not documented is rollback. The README does not describe how to return to a previous image, and with no releases retrieved there is no obvious tag to pin to. If you want a reversible upgrade path, resolve the image tag question before you deploy, not after a bad pull. The `QWEN2API_IMAGE` variable in the compose file exists precisely so you can override the default image reference.
On maintenance cost, the environment file is the best proxy. It exposes roughly thirty tunables spanning timeouts, retries, cooldowns, prewarm behaviour and context size limits. That is not a set-and-forget configuration surface. Budget time for tuning it against your actual traffic, particularly the jitter and interval settings that default to zero.
Editorial conclusion
Adopt qwen2API if you already hold Qwen web accounts, want OpenAI, Anthropic or Gemini SDK compatibility without changing client code, and can accept that the upstream is a web protocol rather than a documented inference API. Do not adopt it if you need a vendor SLA, a published rate limit, or a licence you can read before shipping: the README badge says GPL-3.0 but the repository has no LICENSE file in its top-level entries. Before rolling it out, verify that ADMIN_KEY is set to a private value, that ./data and ./logs persist across a container replacement, and that your Qwen accounts survive the per-account cooldown logic under your real request rate.
Frequently asked questions
What is qwen2API and what does it do?
It is a self-hosted gateway that converts Qwen Web capabilities into OpenAI, Anthropic and Gemini compatible API protocols. It also ships a local WebUI for managing accounts, downstream API keys, runtime settings and model tests.
How do I install qwen2API with Docker?
The README recommends pulling the Docker Hub image. Create a directory with `data` and `logs` subdirectories, write a `.env` with `HOST_PORT`, `HOST_DATA_DIR`, `HOST_LOGS_DIR` and `ADMIN_KEY`, add the compose file, then run `docker compose pull` and `docker compose up -d`.
Which API endpoints does qwen2API expose?
On the OpenAI side it lists `/v1/chat/completions`, `/v1/responses`, `/v1/models`, `/v1/files`, `/v1/images/generations` and `/v1/videos/generations`. It also provides `/v1/messages`, `/anthropic/v1/messages` and `/v1/messages/count_tokens` for Anthropic clients, and `/v1beta/models/{model}:generateContent` plus the streaming variant for Gemini clients.
What port does qwen2API listen on?
The image sets `PORT=7860` and the compose file maps `${HOST_PORT:-7860}` to it. The health check is at `/healthz` and the keepalive probe at `/keepalive` on the same port.
Why do my environment-injected Qwen accounts not appear in the qwen2API WebUI?
Accounts passed through `QWEN_ACCOUNT_N` are runtime-only and are not written to `data/accounts.json`, so they do not show up as editable WebUI entries. The same applies to keys injected through `QWEN_API_KEY`, `QWEN_API_KEYS` or `QWEN_API_KEY_N`, which are not saved to `data/api_keys.json` and cannot be deleted from the WebUI.
What licence does qwen2API use?
The README badge indicates GPL-3.0, but the repository's top-level entries do not include a LICENSE file. Treat the badge as a signal rather than the licence text and confirm the terms before redistributing or embedding the project.
Community notes