docs: add architecture docs and centralize documentation index; update READMEs and API links
DS2API
DS2API converts DeepSeek Web chat capability into OpenAI-compatible, Claude-compatible, and Gemini-compatible APIs. The backend is a pure Go implementation, with a React WebUI admin panel (source in webui/, build output auto-generated to static/admin during deployment).
Documentation entry: Docs Index / Architecture / API Reference
Important Disclaimer
This repository is provided for learning, research, personal experimentation, and internal validation only. It does not grant any commercial authorization and comes with no warranty of fitness, stability, or results.
The author and repository maintainers are not responsible for any direct or indirect loss, account suspension, data loss, legal risk, or third-party claims arising from use, modification, distribution, deployment, or reliance on this project.
Do not use this project in ways that violate service terms, agreements, laws, or platform rules. Before any commercial use, review the
LICENSE, the relevant terms, and confirm that you have the author's written permission.
Architecture Overview (Summary)
flowchart LR
Client["🖥️ Clients / SDKs\n(OpenAI / Claude / Gemini)"]
Upstream["☁️ DeepSeek API"]
subgraph DS2API["DS2API 3.x (Unified OpenAI Core)"]
Router["chi Router + Middleware\n(RequestID / RealIP / Logger / Recoverer / CORS)"]
subgraph Adapters["Protocol Adapters"]
OA["OpenAI\n/v1/*"]
CA["Claude\n/anthropic/* + /v1/messages"]
GA["Gemini\n/v1beta/models/* + /v1/models/*"]
Admin["Admin API\n/admin/*"]
WebUI["WebUI\n/admin (static hosting)"]
end
subgraph Runtime["Runtime + Core Capabilities"]
Bridge["CLIProxy Bridge\n(multi-protocol <-> OpenAI)"]
OAEngine["OpenAI ChatCompletions\n(unified tools + stream semantics)"]
Auth["Auth Resolver\n(API key / bearer / x-goog-api-key)"]
Pool["Account Pool + Queue\n(in-flight slots + wait queue)"]
DSClient["DeepSeek Client\n(session / auth / HTTP)"]
Pow["PoW Solver\n(Pure Go ms-level)"]
Tool["Tool Sieve\n(Go/Node semantic parity)"]
end
end
Client --> Router
Router --> OA & CA & GA
Router --> Admin
Router --> WebUI
OA --> OAEngine
CA & GA --> Bridge
Bridge --> OAEngine
OAEngine --> Auth
OAEngine -.account rotation.-> Pool
OAEngine -.tool-call parsing.-> Tool
OAEngine -.PoW solving.-> Pow
Auth --> DSClient
DSClient --> Upstream
Upstream --> DSClient
OAEngine --> Bridge
Bridge --> Client
For the full module-by-module architecture and directory responsibilities, see docs/ARCHITECTURE.en.md.
- Backend: Go (
cmd/ds2api/,api/,internal/), no Python runtime - Frontend: React admin panel (
webui/), served as static build at runtime - Deployment: local run, Docker, Vercel serverless, Linux systemd
3.X Architecture Changes (vs older releases)
- Unified routing core: all protocol entries are now centralized through
internal/server/router.go, with OpenAI / Claude / Gemini / Admin / WebUI routes registered in one tree to avoid multi-entry drift. - Unified execution chain: Claude/Gemini entries are translated by
internal/translatorcliproxy, then executed throughopenai.ChatCompletionsfor shared tool-calling and stream semantics, then translated back to the client protocol. - Cleaner adapter boundaries:
internal/adapter/{claude,gemini}handles protocol wrappers, whileinternal/adapter/openairemains the execution core; upstream DeepSeek calls are retained only in the OpenAI core. - Tool-calling parity across runtimes: Go (
internal/toolcall) and Vercel Node (internal/js/helpers/stream-tool-sieve) follow aligned parsing/anti-leak semantics across JSON / XML / invoke / text-kv inputs. - Config/runtime separation: static config (
config) and runtime policy (settings) are managed independently via Admin APIs, enabling hot updates and password rotation with JWT invalidation. - Streaming behavior upgrade:
/v1/responsesand/v1/chat/completionsnow share a more consistent incremental tool-call emission strategy across SDK ecosystems. - Improved operability:
/healthz,/readyz,/admin/version, and/admin/dev/capturesform a tighter post-deploy diagnostics loop.
Key Capabilities
| Capability | Details |
|---|---|
| OpenAI compatible | GET /v1/models, GET /v1/models/{id}, POST /v1/chat/completions, POST /v1/responses, GET /v1/responses/{response_id}, POST /v1/embeddings |
| Claude compatible | GET /anthropic/v1/models, POST /anthropic/v1/messages, POST /anthropic/v1/messages/count_tokens (plus shortcut paths /v1/messages, /messages) |
| Gemini compatible | POST /v1beta/models/{model}:generateContent, POST /v1beta/models/{model}:streamGenerateContent (plus /v1/models/{model}:* paths) |
| Multi-account rotation | Auto token refresh, email/mobile dual login |
| Concurrency control | Per-account in-flight limit + waiting queue, dynamic recommended concurrency |
| DeepSeek PoW | Pure Go high-performance solver (DeepSeekHashV1), ms-level response |
| Tool Calling | Anti-leak handling: non-code-block feature match, early delta.tool_calls, structured incremental output |
| Admin API | Config management, runtime settings hot-reload, account testing/batch test, session cleanup, import/export, Vercel sync, version check |
| WebUI Admin Panel | SPA at /admin (bilingual Chinese/English, dark mode) |
| Health Probes | GET /healthz (liveness), GET /readyz (readiness) |
Platform Compatibility Matrix
| Tier | Platform | Status |
|---|---|---|
| P0 | Codex CLI/SDK (wire_api=chat / wire_api=responses) |
✅ |
| P0 | OpenAI SDK (JS/Python, chat + responses) | ✅ |
| P0 | Vercel AI SDK (openai-compatible) | ✅ |
| P0 | Anthropic SDK (messages) | ✅ |
| P0 | Google Gemini SDK (generateContent) | ✅ |
| P1 | LangChain / LlamaIndex / OpenWebUI (OpenAI-compatible integration) | ✅ |
Model Support
OpenAI Endpoint
| Model | thinking | search |
|---|---|---|
deepseek-chat |
❌ | ❌ |
deepseek-reasoner |
✅ | ❌ |
deepseek-chat-search |
❌ | ✅ |
deepseek-reasoner-search |
✅ | ✅ |
Claude Endpoint
| Model | Default Mapping |
|---|---|
claude-sonnet-4-5 |
deepseek-chat |
claude-haiku-4-5 (compatible with claude-3-5-haiku-latest) |
deepseek-chat |
claude-opus-4-6 |
deepseek-reasoner |
Override mapping via claude_mapping or claude_model_mapping in config.
In addition, /anthropic/v1/models now includes historical Claude 1.x/2.x/3.x/4.x IDs and common aliases for legacy client compatibility.
Claude Code integration pitfalls (validated)
- Set
ANTHROPIC_BASE_URLto the DS2API root URL (for examplehttp://127.0.0.1:5001). Claude Code sends requests to/v1/messages?beta=true. ANTHROPIC_API_KEYmust match an entry inkeysfromconfig.json. Keeping both a regular key and ansk-ant-*style key improves client compatibility.- If your environment has proxy variables, set
NO_PROXY=127.0.0.1,localhost,<your_host_ip>for DS2API to avoid proxy interception of local traffic. - If tool calls are rendered as plain text and not executed, upgrade to a build that includes multi-format Claude tool-call parsing (JSON/XML/ANTML/invoke).
Gemini Endpoint
The Gemini adapter maps model names to DeepSeek native models via model_aliases or built-in heuristics, supporting both generateContent and streamGenerateContent call patterns with full Tool Calling support (functionDeclarations → functionCall output).
Quick Start
Universal First Step (all deployment modes)
Use config.json as the single source of truth (recommended):
cp config.example.json config.json
# Edit config.json
Recommended per deployment mode:
- Local run: read
config.jsondirectly - Docker / Vercel: generate Base64 from
config.jsonand inject asDS2API_CONFIG_JSON, or paste raw JSON directly
Option 1: Local Run
Prerequisites: Go 1.26+, Node.js 20.19+ or 22.12+ (only if building WebUI locally)
# 1. Clone
git clone https://github.com/CJackHwang/ds2api.git
cd ds2api
# 2. Configure
cp config.example.json config.json
# Edit config.json with your DeepSeek account info and API keys
# 3. Start
go run ./cmd/ds2api
Default local URL: http://127.0.0.1:5001
The server actually binds to 0.0.0.0:5001, so devices on the same LAN can usually reach it through your private IP as well.
WebUI auto-build: On first local startup, if
static/adminis missing, DS2API will auto-runnpm ci(only when dependencies are missing) andnpm run build -- --outDir static/admin --emptyOutDir(requires Node.js). You can also build manually:./scripts/build-webui.sh
Option 2: Docker
# 1. Prepare env file and config file
cp .env.example .env
cp config.example.json config.json
# 2. Edit .env (at least set DS2API_ADMIN_KEY; optionally set DS2API_HOST_PORT to change the host port)
# DS2API_ADMIN_KEY=replace-with-a-strong-secret
# 3. Start
docker-compose up -d
# 4. View logs
docker-compose logs -f
The default docker-compose.yml maps host port 6011 to container port 5001. If you want 5001 exposed directly, set DS2API_HOST_PORT=5001 (or adjust the ports mapping).
Rebuild after updates: docker-compose up -d --build
Zeabur One-Click (Dockerfile)
- Click the “Deploy on Zeabur” button above to deploy.
- After deployment, open
/adminand login withDS2API_ADMIN_KEYshown in Zeabur env/template instructions. - Import / edit config in Admin UI (it will be written and persisted to
/data/config.json).
Note: when Zeabur builds directly from the repo Dockerfile, you do not need to pass BUILD_VERSION. The image prefers that build arg when provided, and automatically falls back to the repo-root VERSION file when it is absent.
Option 3: Vercel
- Fork this repo to your GitHub account
- Import the project on Vercel
- Set environment variables (minimum:
DS2API_ADMIN_KEY; recommended to also setDS2API_CONFIG_JSON) - Deploy
Recommended first step in repo root:
cp config.example.json config.json
# Edit config.json
Recommended: convert config.json to Base64 locally, then paste into DS2API_CONFIG_JSON to avoid JSON formatting mistakes:
base64 < config.json | tr -d '\n'
Streaming note:
/v1/chat/completionson Vercel is routed toapi/chat-stream.js(Node Runtime) for real-time SSE. Auth, account selection, and session/PoW preparation are still handled by the Go internal prepare endpoint; streaming output (includingtools) is assembled on Node with Go-aligned anti-leak handling.
For detailed deployment instructions, see the Deployment Guide.
Option 4: Download Release Binaries
GitHub Actions automatically builds multi-platform archives on each Release:
# After downloading the archive for your platform
tar -xzf ds2api_<tag>_linux_amd64.tar.gz
cd ds2api_<tag>_linux_amd64
cp config.example.json config.json
# Edit config.json
./ds2api
Option 5: OpenCode CLI
- Copy the example config:
cp opencode.json.example opencode.json
- Edit
opencode.json:
- Set
baseURLto your DS2API endpoint (for example,https://your-domain.com/v1) - Set
apiKeyto your DS2API key (fromconfig.keys)
- Start OpenCode CLI in the project directory (run
opencodeusing your installed method).
Recommended: use the OpenAI-compatible path (
/v1/*) via@ai-sdk/openai-compatibleas shown in the example. If your client supportswire_api, test bothresponsesandchat; DS2API supports both paths.
Configuration
config.json Example
{
"keys": ["your-api-key-1", "your-api-key-2"],
"accounts": [
{
"email": "user@example.com",
"password": "your-password"
},
{
"mobile": "12345678901",
"password": "your-password"
}
],
"model_aliases": {
"gpt-4o": "deepseek-chat",
"gpt-5-codex": "deepseek-reasoner",
"o3": "deepseek-reasoner"
},
"compat": {
"wide_input_strict_output": true,
"strip_reference_markers": true
},
"responses": {
"store_ttl_seconds": 900
},
"embeddings": {
"provider": "deterministic"
},
"claude_mapping": {
"fast": "deepseek-chat",
"slow": "deepseek-reasoner"
},
"admin": {
"jwt_expire_hours": 24
},
"runtime": {
"account_max_inflight": 2,
"account_max_queue": 0,
"global_max_inflight": 0,
"token_refresh_interval_hours": 6
},
"auto_delete": {
"mode": "none"
}
}
keys: API access keys; clients authenticate viaAuthorization: Bearer <key>accounts: DeepSeek account list, supportsemailormobilelogintoken: Even if set inconfig.json, it is cleared during load (DS2API does not read persisted tokens from config); runtime tokens are maintained/refreshed in memory onlymodel_aliases: Map common model names (GPT/Codex/Claude) to DeepSeek modelscompat.wide_input_strict_output: Keeptrue(current default policy)compat.strip_reference_markers: Keeptrue; it strips reference markers from visible outputtoolcall: Legacy field; the current behavior is fixed to feature matching + high-confidence early emit, and any config value is ignoredresponses.store_ttl_seconds: In-memory TTL for/v1/responses/{id}embeddings.provider: Embeddings provider (deterministic/mock/builtinbuilt-in)claude_mapping: Mapsfast/slowsuffixes to corresponding DeepSeek models (still compatible withclaude_model_mapping)admin: Admin panel settings (JWT expiry, password hash, etc.), hot-reloadable via Admin Settings APIruntime: Runtime parameters (concurrency limits, queue sizes, managed token refresh interval), hot-reloadable via Admin Settings API;account_max_queue=0/global_max_inflight=0means auto-calculate from recommended values,token_refresh_interval_hours=6is the default forced re-login intervalauto_delete.mode: How to clean up DeepSeek remote chat records after each request completes. Supported values:none(default, no deletion),single(delete only the current session),all(delete all sessions); legacyauto_delete.sessions=trueis still treated asall
Environment Variables
| Variable | Purpose | Default |
|---|---|---|
PORT |
Service port | 5001 |
LOG_LEVEL |
Log level | INFO (DEBUG/WARN/ERROR) |
DS2API_ADMIN_KEY |
Admin login key | admin |
DS2API_JWT_SECRET |
Admin JWT signing secret | Same as DS2API_ADMIN_KEY |
DS2API_JWT_EXPIRE_HOURS |
Admin JWT TTL in hours | 24 |
DS2API_CONFIG_PATH |
Config file path | config.json |
DS2API_CONFIG_JSON |
Inline config (JSON or Base64) | — |
DS2API_ENV_WRITEBACK |
Auto-write env-backed config to file and transition to file mode (1/true/yes/on) |
Disabled |
DS2API_STATIC_ADMIN_DIR |
Admin static assets dir | static/admin |
DS2API_AUTO_BUILD_WEBUI |
Auto-build WebUI on startup | Enabled locally, disabled on Vercel |
DS2API_ACCOUNT_MAX_INFLIGHT |
Max in-flight requests per account | 2 |
DS2API_ACCOUNT_MAX_QUEUE |
Waiting queue limit | recommended_concurrency |
DS2API_GLOBAL_MAX_INFLIGHT |
Global max in-flight requests | recommended_concurrency |
DS2API_VERCEL_INTERNAL_SECRET |
Vercel hybrid streaming internal auth | Falls back to DS2API_ADMIN_KEY |
DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS |
Stream lease TTL seconds | 900 |
DS2API_DEV_PACKET_CAPTURE |
Local dev packet capture switch (record recent request/response bodies) | Enabled by default on non-Vercel local runtime |
DS2API_DEV_PACKET_CAPTURE_LIMIT |
Number of captured sessions to retain (auto-evict overflow) | 20 |
DS2API_DEV_PACKET_CAPTURE_MAX_BODY_BYTES |
Max recorded bytes per captured response body | 5242880 |
VERCEL_TOKEN |
Vercel sync token | — |
VERCEL_PROJECT_ID |
Vercel project ID | — |
VERCEL_TEAM_ID |
Vercel team ID | — |
DS2API_VERCEL_PROTECTION_BYPASS |
Vercel deployment protection bypass for internal Node→Go calls | — |
Note: when
DS2API_CONFIG_JSONis detected, the Admin UI shows mode risk and auto-persistence status (includingDS2API_CONFIG_PATHand mode-transition hints).
Authentication Modes
For business endpoints (/v1/*, /anthropic/*, Gemini routes), DS2API supports two modes:
| Mode | Description |
|---|---|
| Managed account | Use a key from config.keys via Authorization: Bearer ... or x-api-key; DS2API auto-selects an account |
| Direct token | If the token is not in config.keys, DS2API treats it as a DeepSeek token directly |
Optional header X-Ds2-Target-Account: Pin a specific managed account (value is email or mobile).
Gemini routes also accept x-goog-api-key, or ?key= / ?api_key= when no auth header is present.
Concurrency Model
Per-account inflight = DS2API_ACCOUNT_MAX_INFLIGHT (default 2)
Recommended concurrency = account_count × per_account_inflight
Queue limit = DS2API_ACCOUNT_MAX_QUEUE (default = recommended concurrency)
429 threshold = inflight + queue ≈ account_count × 4
- When inflight slots are full, requests enter a waiting queue — no immediate 429
- 429 is returned only when total load exceeds inflight + queue capacity
GET /admin/queue/statusreturns real-time concurrency state
Tool Call Adaptation
When tools is present in the request, DS2API performs anti-leak handling:
- Toolcall feature matching is enabled only in non-code-block context (fenced examples are ignored)
- The parser prioritizes XML/Markup, while also accepting JSON / ANTML / invoke / text-kv, and normalizes everything into the internal tool-call structure
responsesstreaming strictly uses official item lifecycle events (response.output_item.*,response.content_part.*,response.function_call_arguments.*)responsessupports and enforcestool_choice(auto/none/required/forced function);requiredviolations return422for non-stream andresponse.failedfor stream- The output protocol follows the client request (OpenAI / Claude / Gemini native shapes); model-side prompting can prefer XML, and the compatibility layer handles the protocol-specific translation
Note: the current parser still prioritizes “parse successfully whenever possible”; hard allow-list rejection for undeclared tool names is not enabled yet.
Local Dev Packet Capture
This is for debugging issues such as Responses reasoning streaming and tool-call handoff. When enabled, DS2API stores the latest N DeepSeek conversation payload pairs (request body + upstream response body), defaulting to 20 entries with auto-eviction; each response body is capped at 5 MB by default.
Enable example:
DS2API_DEV_PACKET_CAPTURE=true \
DS2API_DEV_PACKET_CAPTURE_LIMIT=20 \
go run ./cmd/ds2api
Inspect/clear (Admin JWT required):
GET /admin/dev/captures: list captured items (newest first)DELETE /admin/dev/captures: clear captured itemsGET /admin/dev/raw-samples/query?q=keyword&limit=20: search current in-memory captures by prompt keyword and groupcompletion + continuebychat_session_idPOST /admin/dev/raw-samples/save: persist a selected capture chain astests/raw_stream_samples/<sample-id>/
Response fields include:
request_body: full payload sent to DeepSeekresponse_body: concatenated raw upstream stream body textresponse_truncated: whether body-size truncation happened
The save endpoint can target a chain by query, chain_key, or capture_id. Example:
{"query":"Guangzhou weather","sample_id":"gz-weather-from-memory"}
Project Structure
ds2api/
├── app/ # Unified HTTP handler assembly (shared by local + serverless)
├── cmd/
│ ├── ds2api/ # Local / container entrypoint
│ └── ds2api-tests/ # End-to-end testsuite entrypoint
├── api/
│ ├── index.go # Vercel Serverless Go entry
│ ├── chat-stream.js # Vercel Node.js stream relay
│ └── (rewrite targets in vercel.json)
├── internal/
│ ├── account/ # Account pool and concurrency queue
│ ├── adapter/
│ │ ├── openai/ # OpenAI adapter (incl. tool call parsing, Vercel stream prepare/release)
│ │ ├── claude/ # Claude adapter
│ │ └── gemini/ # Gemini adapter (generateContent / streamGenerateContent)
│ ├── admin/ # Admin API handlers (incl. Settings hot-reload)
│ ├── auth/ # Auth and JWT
│ ├── claudeconv/ # Claude message format conversion
│ ├── compat/ # Go-version compatibility and regression helpers
│ ├── config/ # Config loading, validation, and hot-reload
│ ├── deepseek/ # DeepSeek API client, PoW logic
│ ├── js/ # Node runtime stream/compat logic
│ ├── devcapture/ # Dev packet capture module
│ ├── rawsample/ # Visible-text extraction and replay helpers for raw stream samples
│ ├── format/ # Output formatting
│ ├── prompt/ # Prompt construction
│ ├── server/ # HTTP routing and middleware (chi router)
│ ├── sse/ # SSE parsing utilities
│ ├── stream/ # Unified stream consumption engine
│ ├── testsuite/ # End-to-end testsuite framework and case orchestration
│ ├── translatorcliproxy/ # CLIProxy bridge and stream writer components
│ ├── toolcall/ # Tool Call parsing, repair, and formatting (core business logic)
│ ├── util/ # Common utilities (Token estimation, JSON helpers, etc.)
│ ├── version/ # Version parsing/comparison and tag normalization
│ └── webui/ # WebUI static file serving and auto-build
├── webui/ # React WebUI source (Vite + Tailwind)
│ └── src/
│ ├── app/ # Routing, auth, config state
│ ├── features/ # Feature modules (account/settings/vercel/apiTester)
│ ├── components/ # Shared UI pieces (login/landing, etc.)
│ └── locales/ # Language packs (zh.json / en.json)
├── scripts/
│ └── build-webui.sh # Manual WebUI build script
├── tests/
│ ├── compat/ # Compatibility fixtures and expected outputs
│ ├── node/ # Node-side unit tests (chat-stream / tool-sieve)
│ ├── raw_stream_samples/ # Raw SSE samples and replay metadata
│ └── scripts/ # Unified test script entrypoints (unit/e2e)
├── docs/ # Deployment / contributing / testing docs
├── static/admin/ # WebUI build output (not committed to Git)
├── .github/
│ ├── workflows/ # GitHub Actions (quality gates + release automation)
│ ├── ISSUE_TEMPLATE/ # Issue templates
│ └── PULL_REQUEST_TEMPLATE.md
├── config.example.json # Config file template
├── .env.example # Environment variable template
├── Dockerfile # Multi-stage build (WebUI + Go)
├── docker-compose.yml # Production Docker Compose
├── docker-compose.dev.yml # Development Docker Compose
├── vercel.json # Vercel routing and build config
└── go.mod / go.sum # Go module dependencies
Documentation Index
| Document | Description |
|---|---|
| API.md / API.en.md | API reference with request/response examples |
| DEPLOY.md / DEPLOY.en.md | Deployment guide (local/Docker/Vercel/systemd) |
| CONTRIBUTING.md / CONTRIBUTING.en.md | Contributing guide |
| TESTING.md | Testsuite guide |
Testing
# Unit tests (Go + Node)
./tests/scripts/run-unit-all.sh
# One-command live end-to-end tests (real accounts, full request/response logs)
./tests/scripts/run-live.sh
# Or with custom flags
go run ./cmd/ds2api-tests \
--config config.json \
--admin-key admin \
--out artifacts/testsuite \
--timeout 120 \
--retries 2
# Release-blocking gates
./tests/scripts/check-stage6-manual-smoke.sh
./tests/scripts/check-refactor-line-gate.sh
./tests/scripts/run-unit-all.sh
npm ci --prefix webui && npm run build --prefix webui
Release Artifact Automation (GitHub Actions)
Workflow: .github/workflows/release-artifacts.yml
- Trigger: only on GitHub Release
published(normal pushes do not trigger builds) - Outputs: multi-platform archives (
linux/amd64,linux/arm64,darwin/amd64,darwin/arm64,windows/amd64) +sha256sums.txt - Container publishing: GHCR only (
ghcr.io/cjackhwang/ds2api) - Each archive includes:
ds2apiexecutable,static/admin, WASM file (with embedded fallback support), config template, README, LICENSE
Disclaimer
This project is built through reverse engineering and is provided for learning, research, personal experimentation, and internal validation only. No commercial authorization is granted, and no warranty of stability, fitness, or results is provided. The author and repository maintainers are not responsible for any direct or indirect loss, account suspension, data loss, legal risk, or third-party claims arising from use, modification, distribution, deployment, or reliance on this project.
Do not use this project in ways that violate service terms, agreements, laws, or platform rules. Before any commercial use, review the LICENSE, the relevant terms, and confirm that you have the author's written permission.