A Node.js HTTP bridge that exposes the Qoder AI coding assistant API as an OpenAI-compatible /v1/chat/completions endpoint. It accepts requests in OpenAI, Anthropic Claude, and Google Gemini formats, translates them to Qoder's internal API, and streams responses back in the caller's native SSE format.
- OpenAI Compatible — Drop-in replacement for any OpenAI SDK client
- Multi-Format Support — Works with OpenAI, Claude (Anthropic), and Gemini clients simultaneously
- Dynamic Model Catalog — Fetches the full Qoder model catalog in real-time (Qwen, DeepSeek, GLM, Kimi, MiniMax, etc.), not limited to a single model
- Account Info API — Query plan, quota, and session status via
/v1/user/statusand/v1/user/quota - Streaming — Full SSE streaming support for all three API formats
- Tool Calls — Handles both structured tool calls and text-based tool call fallback parsing
- API Key Auth — Optional Bearer token authentication for production deployments
- Proxy Support — HTTP CONNECT and SOCKS5 proxy support (no external dependencies)
- Docker Ready — Multi-arch Docker images published to GitHub Container Registry
| Format | Endpoints |
|---|---|
| OpenAI | /v1/chat/completions, /v1/models, /v1/completions, /v1/responses, /v1/messages, /v1/user/status, /v1/user/quota |
| OpenAI Prefix | /openai/v1/chat/completions, /openai/v1/models |
| Claude | /claude/v1/messages, /v1/messages (Anthropic Messages API) |
| Gemini | /gemini/v1beta/models, /gemini/v1beta/models/{model}:generateContent, /gemini/v1beta/models/{model}:streamGenerateContent |
Models are fetched dynamically from the Qoder API at runtime. GET /v1/models returns the chat-scene catalog only (other Qoder scenes — quest, nap, qwork, experts, qwake, assistant, inline — use different API surfaces that this bridge does not implement). Each entry carries metadata the frontend can use to render the model picker:
| Field | Meaning |
|---|---|
id |
Internal key used as the model parameter (e.g. qmodel_latest) |
display_name |
Human-readable name (e.g. Qwen3.7-Max) |
enable |
true if the caller's plan can use this model right now |
is_free |
true if the model is free-tier (available to PLAN_TIER_FREE) |
is_default |
true if this is the default chat model |
is_new |
true if the model is flagged as new in the catalog |
is_reasoning / is_vl |
Reasoning and vision capabilities |
architecture |
OpenRouter-style modality hint; input_modalities includes "image" for vision (is_vl) models |
price_factor |
Current price factor (may be discounted; original_price_factor is included when different) |
max_input_tokens |
Maximum input context size |
context_config |
Available context window options (when the model supports switching) |
thinking_config |
Available thinking/effort options (when the model supports it) |
Image input (vision): all vision (is_vl) models accept images. Send them the usual way for each API — OpenAI image_url parts, Claude image blocks (base64 or url), or Gemini inlineData — and the bridge forwards them to the model as image_url content (base64 data-URIs work; no upload step needed).
The catalog includes models such as:
| Model | Key | Reasoning | Vision | Notes |
|---|---|---|---|---|
| Qwen3.7-Max | qmodel_latest |
- | Y | Default, free-tier |
| Qwen3.7-Plus | qmodel |
Y | Y | |
| DeepSeek-V4-Pro | dmodel |
Y | Y | |
| DeepSeek-V4-Flash | dfmodel |
Y | Y | |
| GLM-5.1 | gm51model |
Y | Y | |
| Kimi-K2.6 | kmodel |
- | Y | |
| MiniMax-M3 | mmodel |
- | Y | |
| Ultimate | ultimate |
Y | Y | Premium tier |
| Performance | performance |
Y | Y | Premium tier |
| Efficient | efficient |
- | Y | Low cost |
Note: Model availability depends on your Qoder plan. Free-tier accounts (
PLAN_TIER_FREE) see only Qwen3.7-Max withenable=true; every other model hasenable=false. UseGET /v1/user/statusto check your plan andGET /v1/modelsto see which models are enabled for your account.
Use the model key (e.g. qmodel_latest, dmodel) or display name (e.g. Qwen3.7-Max, DeepSeek-V4-Pro) as the model parameter in chat requests.
- Node.js >= 18
- A Qoder personal access token (
QODER_PAT)
# Clone the repository
git clone https://github.com/emptysuns/qoder2api.git
cd qoder2api
# Install dependencies
npm install
# Run
QODER_PAT=your_token npm startThe server starts on http://127.0.0.1:8963 by default.
# Auto-restart on file changes
QODER_PAT=your_token npm run dev| Variable | Required | Default | Description |
|---|---|---|---|
QODER_PAT |
Yes | — | Qoder personal access token(s). Supports comma-separated multiple tokens for round-robin load balancing (e.g. token1,token2,token3). |
API_KEY |
No | — | API key for Bearer Token auth. When set, all /v1/* requests require Authorization: Bearer <key>. Public endpoints (/health, /version, /auth/login) remain open. |
LOG_LEVEL |
No | info |
Log verbosity: debug, info, warn, or error. |
QODER_HOST |
No | 127.0.0.1 |
HTTP server listen address |
QODER_PORT |
No | 8963 |
HTTP server listen port |
HTTPS_PROXY |
No | — | Proxy URL for upstream connections. Supports http://, https://, socks5://. Also checks: https_proxy, HTTP_PROXY, http_proxy, SOCKS_PROXY, socks_proxy, ALL_PROXY, all_proxy. |
# With API key authentication
QODER_PAT=your_token API_KEY=sk-your-secret-key npm start
# With multiple PATs (round-robin load balancing)
QODER_PAT=token1,token2,token3 npm start
# With SOCKS5 proxy
QODER_PAT=your_token HTTPS_PROXY=socks5://127.0.0.1:1080 npm start
# With HTTP proxy
QODER_PAT=your_token HTTPS_PROXY=http://127.0.0.1:7890 npm start
# Custom host and port
QODER_PAT=your_token QODER_HOST=0.0.0.0 QODER_PORT=3000 npm start
# Enable debug logging
QODER_PAT=your_token LOG_LEVEL=debug npm start
# Only show warnings and errors
QODER_PAT=your_token LOG_LEVEL=warn npm startConfigure multiple Qoder accounts with comma-separated PATs to distribute requests across them using round-robin:
QODER_PAT=pt-token-aaa,pt-token-bbb,pt-token-ccc npm start- Each incoming request is assigned to the next account in rotation
- All accounts are authenticated at startup; failed accounts will cause startup to fail
- Single token still works as before (backward compatible)
docker build --platform linux/amd64 -t qoder2api .
docker run -e QODER_PAT=your_token -p 8963:8963 qoder2apiCreate a .env file:
QODER_PAT=your_token
API_KEY=sk-your-secret-key
LOG_LEVEL=infoThen run:
docker compose upMulti-arch images (linux/amd64, linux/arm64) are published to GitHub Container Registry on every v* tag:
docker pull ghcr.io/emptysuns/qoder2api:latest# Check plan and quota
curl http://127.0.0.1:8963/v1/user/status \
-H "Authorization: Bearer sk-your-secret-key"
# List all available models
curl http://127.0.0.1:8963/v1/models \
-H "Authorization: Bearer sk-your-secret-key"from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8963/v1",
api_key="sk-your-secret-key" # your API_KEY, or "anything" if not set
)
# Use any model from the catalog (check /v1/models for available ones)
response = client.chat.completions.create(
model="Qwen3.7-Max", # or "DeepSeek-V4-Pro", "qmodel_latest", "dmodel", etc.
messages=[{"role": "user", "content": "Hello!"}],
stream=True
)
for chunk in response:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")import anthropic
client = anthropic.Anthropic(
base_url="http://127.0.0.1:8963/claude",
api_key="sk-your-secret-key"
)
message = client.messages.create(
model="Qwen3.7-Max",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}]
)
print(message.content[0].text)curl http://127.0.0.1:8963/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk-your-secret-key" \
-d '{
"model": "Qwen3.7-Max",
"messages": [{"role": "user", "content": "Hello!"}],
"stream": true
}'| Method | Path | Description |
|---|---|---|
| GET | /health |
Health check — returns { "status": "ok" } |
| GET | /version |
Version info — returns { "version": "x.y.z" } |
| POST | /auth/login |
Login stub — returns { "status": "ok" } |
| Method | Path | Description |
|---|---|---|
| GET | /v1/models |
List available models from Qoder's real-time catalog (includes enable status, pricing, context windows) |
| Method | Path | Description |
|---|---|---|
| GET | /v1/user/status |
User plan, quota, feature switches, and session info |
| GET | /v1/user/quota |
Quota summary (remaining credits, plan tier, exceeded status) |
| Method | Path | Description |
|---|---|---|
| POST | /v1/chat/completions |
OpenAI chat completions |
| POST | /v1/completions |
Alias for chat completions |
| POST | /v1/complete |
Alias for chat completions |
| Method | Path | Description |
|---|---|---|
| POST | /claude/v1/messages |
Anthropic Messages API |
| POST | /v1/messages |
Anthropic Messages API (shared endpoint) |
| Method | Path | Description |
|---|---|---|
| GET | /gemini/v1beta/models |
List Gemini models |
| POST | /gemini/v1beta/models/:model/generateContent |
Non-streaming generation |
| POST | /gemini/v1beta/models/:model/streamGenerateContent |
Streaming generation |
OpenAI client → POST /v1/chat/completions ─┐
Claude client → POST /claude/v1/messages ├→ Fastify server on :8963
Gemini client → POST /gemini/v1beta/models │ → normalizes request to OpenAI format
.../streamGenerateContent ─┘ → translates messages to Qoder format
→ BearerApiClient signs + sends to Qoder SSE API
→ StreamAccumulator converts Qoder SSE deltas
back to client's native SSE format
| Module | Description |
|---|---|
src/index.js |
Entry point. Reads env vars, creates bridge, starts server. |
src/bridge.js |
Core OpenAiBridge class. Fastify HTTP server with multi-format routing, message translation, SSE streaming, tool call handling. |
src/bearer-client.js |
BearerApiClient: authenticated HTTP client with COSY bearer token signing. |
src/bearer-builder.js |
Session context construction and bearer token signing (RSA + AES + MD5). |
src/proxy.js |
Proxy-aware HTTPS helper. Supports HTTP CONNECT and SOCKS5 with no external dependencies. |
src/signature-client.js |
Exchanges PAT for job token via Qoder's auth center. |
src/signature.js |
Static request signing for the auth center. |
src/encoding.js |
Custom base64 variant used for all Qoder API payloads. |
src/logger.js |
Leveled logger (debug/info/warn/error). Controlled via LOG_LEVEL env var. |
src/local-auth.js |
Reads cached auth from ~/.qoder/.auth/. |
When API_KEY is set, all AI endpoints require Authorization: Bearer <key>. The server uses timing-safe string comparison to prevent timing attacks. Public endpoints (/health, /version, /auth/login) remain accessible without authentication.
Recommendations:
- Always set
API_KEYin production - Use
QODER_HOST=127.0.0.1(default) unless you need external access - Use a reverse proxy (nginx, Caddy) with TLS for public deployments
See LICENSE for details.
