OpenAI-compatible proxy for chat.deepseek.com
Zero npm dependencies Β· Node.js 18+ Β· Native PoW (WASM) Β· Streaming Β· Emulated tool calling Β· Token pool
ββββββββββββββββ OpenAI /v1/chat/completions ββββββββββββββββββββββββ PoW + Bearer βββββββββββββββββββββ
β App / Agent β ββββββββββββββββββββββββββββββββββββΆ β Free DeepSeek API β βββββββββββββββββββββΆ β chat.deepseek.com β
β (SDK, IDE) β βββββββββββ SSE / JSON βββββββββββββ β (this proxy) β ββββββ SSE/JSON βββββ β (web endpoint) β
ββββββββββββββββ ββββββββββββββββββββββββ βββββββββββββββββββββ
Quick start Β· How it works Β· API Β· Tool calling Β· Agent tutorials Β· Configuration
| Feature | Details | |
|---|---|---|
| β‘ | True streaming | Relays DeepSeek SSE as OpenAI chat.completion.chunk deltas (no global buffering). |
| π§ | Reasoning | deepseek-reasoner (thinking) β reasoning_content in stream and non-stream responses. |
| π οΈ | Emulated tool calling | tools, tool_choice, parallel_tool_calls, tool-result round-trips β JSON + DSML parsing with stream hold-back to prevent leaks. |
| π§± | Extended context | Advertised window up to 1M tokens (effective limit depends on the upstream endpoint/account). |
| π | Native PoW | Solves DeepSeekHashV1 via the original WASM (wasm_solve), ~30β110 ms. |
| π | Resilience | Round-robin token pool, hot-reload, rate limiting + jitter/gap/cooldown, one in-flight completion per token, pre-warming, watchdog, optional auto re-login. |
| π | Observability | /health (masked status + metrics), /metrics (Prometheus/JSON), X-Request-Id, X-Ds-* timing headers. |
| π§© | Drop-in | OpenAI SDKs, curl, and agent integrations (OpenCode, Cursor, Continue, Cline, Aider, Zed, etc.). |
Note
The proxy uses the web endpoint chat.deepseek.com with userTokens. Keep traffic moderate and compliant with DeepSeek policies: automated usage may lead to rate limits or account suspension.
git clone https://github.com/zeroaiproject/Free-DeepSeek-API.git
cd free-deepseek-api
cp .env.example .env # optional (auto-loaded)
echo "$DEEPSEEK_USER_TOKEN" > tokens.txt
./run.sh # or: npm start | docker compose up -dcurl http://127.0.0.1:8787/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "Say hello in one sentence."}]
}'Streaming, health, models
# streaming SSE
curl -N http://127.0.0.1:8787/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"deepseek-chat","stream":true,"messages":[{"role":"user","content":"Count from 1 to 5."}]}'
# health + metrics (masked tokens)
curl http://127.0.0.1:8787/health
# models & capabilities
curl http://127.0.0.1:8787/v1/modelsThe proxy requires a userToken (stored by the DeepSeek web app after login).
| Method | Command / action | Notes |
|---|---|---|
| Browser | localStorage.getItem('userToken') at https://chat.deepseek.com |
Paste as-is (base64, do not URL-encode) into tokens.txt (one per line). |
| Email + password | node scripts/login.js email password |
Works if the account has a password. |
| Signup (experimental) | node scripts/signup.js --email ... --mailbox imap --captcha ... |
Guest PoW automated; server-verified hCaptcha + accessible mailbox required. |
Google accounts without a password: set one via "forgot password" (one-time manual step) to enable automatic re-login (
DEEPSEEK_EMAIL/PASSWORD). Details and troubleshooting: tutos/SETUP.md.
The proxy reproduces the chat.deepseek.com web flow and exposes it as an OpenAI-compatible API. At a high level: bearer auth (userToken) β per-request PoW β chat session β SSE completion β OpenAI translation.
Client (SDK/agent) Local proxy DeepSeek web
β β β
β POST /v1/chat/completions β β
ββββββββββββββββββββββββββββββββββββββΆβ create_pow_challenge β
β ββββββββββββββββββββββββββββββββββββΆβ
β βββββββββββ challenge βββββββββββββββ€
β β solve PoW (WASM, ~30β110ms) β
β β create/reuse chat_session β
β ββββββββββββββββββββββββββββββββββββΆβ
β β POST /chat/completion (SSE) β
β ββββββββββββββββββββββββββββββββββββΆβ
β SSE chunks / OpenAI JSON ββββββ fragments / patches ββββββββββ€
βββββββββββββββββββββββββββββββββββββββ assemble + map (THINK/tools) β
Each completion requires an X-Ds-Pow-Response header:
- Challenge β
POST /api/v0/chat/create_pow_challengewithtarget_pathβalgorithm, challenge, salt, signature, difficulty, expire_at, target_path. - Solve β find
answer β [0, difficulty]such thatDeepSeekHash(salt_expireAt_answer) == challenge.- Custom Keccak algorithm embedded in
lib/sha3_wasm_bg.7b9ca65ddd.wasm(executed as-is, not standard SHA-256/SHA3).
- Custom Keccak algorithm embedded in
- Proof β Base64-encoded JSON:
{"algorithm","challenge","salt","answer","signature","target_path"}.
Unsolved challenges can be pre-warmed (TTL ~300 s); a solved proof is single-use.
- Session β
POST /api/v0/chat_session/createreturns achat_session.idreused for a few requests then rotated (latency/noise trade-off). - Flattened transcript β the proxy converts OpenAI messages (system/user/assistant/tool) into the text prompt expected by the web endpoint. It advertises up to a 1M token window (
/v1/models), but the effective limit depends on the upstream endpoint/account. - Completion β
POST /api/v0/chat/completionwithmodel: default|thinking, prompt, and params; response is SSE.
The DeepSeek stream (snapshots response.fragments, appends {"v":"..."}, targeted replacements/patches, status events) is reassembled and mapped:
THINKfragments βreasoning_content(deepseek-reasoner)- text fragments β
delta.content/message.content - end of stream β
finish_reason(stop/tool_calls), relayed aggregateusage
The web endpoint has no native function calling; the proxy emulates it:
- Injects
toolsschemas into a system instruction block (names, JSON Schema, rules,tool_choice/parallel_tool_calls). - Parses replies into OpenAI
tool_callsfrom JSON ({"tool_calls":[...]}) or native DSML markup (<ο½DSMLο½ invoke/parameter>), with argument normalization/coercion. - Stream-safe:
streamgateholds back a short tail (STREAM_HOLD_BACK) and stops text emission once a tool-call marker is detected to prevent markup leaks. - Round-trip:
role:"tool"messages are reinjected into the transcript for subsequent turns.
- Token pool: round-robin, hot-reload of
tokens.txt, masked in/health, penalties/cooldowns. - Rate limiting: bucket + min gap + jitter + escalating cooldown, 1 in-flight completion per token, staggered pre-warming of challenges/sessions (absorbs silent throttling and reduces bursty patterns).
- Watchdog: jittered liveness probes, distinguishes revocation (recoverable
40003) from suspension (not recoverable), optional auto re-login when credentials are configured. - Observability:
/health,/metrics(Prometheus/JSON),X-Request-Id,X-Ds-*timings, optional lightweight logs without bodies/secrets.
| Method | Path | Description |
|---|---|---|
GET |
/, /v1 |
Service info (HTML for browsers, JSON for clients) |
GET |
/health |
Health, uptime, tokens (masked), warm/rate/watchdog/auth, metrics |
GET |
/metrics |
Prometheus (text/plain) or JSON (Accept: application/json) |
GET |
/v1/models |
Models and capabilities |
POST |
/v1/chat/completions |
Chat completions (stream/non-stream), tools |
| Model | Mode | Reasoning | Tools | Advertised context | Max output (hint) |
|---|---|---|---|---|---|
deepseek-chat |
default |
β | β (emulated) | 1M | 8192 |
deepseek-reasoner |
thinking |
reasoning_content |
β (emulated) | 1M | 32768 |
Effective window depends on upstream acceptance.
usagereflects upstream aggregate (no prompt/completion split).
curl http://127.0.0.1:8787/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "deepseek-chat",
"messages": [{"role":"user","content":"What is the weather in Paris?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}
}
}]
}'Details (formats, tool choice, streaming)
| Field | Behavior |
|---|---|
tool_choice: "auto" / omitted |
model decides |
tool_choice: "none" |
tools disabled, text response |
tool_choice: "required" |
at least one call required |
tool_choice: {"type":"function","function":{"name":"X"}} |
force tool X |
parallel_tool_calls: false |
keep only the first call |
- Parsed outputs: JSON and DSML (fullwidth
ο½DSMLο½separators). Arguments are normalized (objects, JSON/double-encoded strings recovered when possible). - During streaming, tool-call markers are never emitted in
content(hold-back). The proxy returns valid OpenAItool_callsand setsfinish_reason: "tool_calls". - Tests:
node scripts/test-parsers.js,node scripts/test-streamgate.js,node scripts/test-tools.js(proxy running).
All tutorials: tutos/ β base
http://127.0.0.1:8787/v1, modelsdeepseek-chat/deepseek-reasoner, dummy keypublicif required.
| Agent / tool | Guide |
|---|---|
| OpenCode | tutos/OPENCODE.md |
| Claude Code (via OpenAI compat) | tutos/CLAUDECODE.md |
| Cursor | tutos/CURSOR.md |
| Windsurf | tutos/WINDSURF.md |
| Cline (VS Code) | tutos/CLINE.md |
| Continue (VS Code / JetBrains) | tutos/CONTINUE.md |
| Aider | tutos/AIDER.md |
| GitHub Copilot (BYOK/compat) | tutos/GITHUB_COPILOT.md |
| Codex CLI & OpenAI CLIs | tutos/CODEX_CLI.md |
| Zed | tutos/ZED.md |
| JetBrains IDEs | tutos/JETBRAINS.md |
| OpenAI SDK (Python/Node/curl) | tutos/OPENAI_SDK.md |
All variables are optional. .env is auto-loaded (without overriding existing vars). Secrets (.env, tokens.txt) are gitignored; tokens are persisted with mode 0600.
| Variable | Default | Purpose |
|---|---|---|
PORT / HOST |
8787 / 0.0.0.0 |
Listen address |
API_KEY |
β | Protect the proxy (Authorization: Bearer) |
DEFAULT_MODEL |
deepseek-chat |
Default model |
DEEPSEEK_TOKENS / DEEPSEEK_TOKENS_FILE |
β / ./tokens.txt |
Token pool (hot-reload) |
DEEPSEEK_EMAIL / DEEPSEEK_PASSWORD / DEEPSEEK_TOKEN_PERSIST |
β | Auto re-login + persistence |
DEEPSEEK_PROXY |
β | Outbound HTTP CONNECT proxy |
WARM_POOL / WARM_SESSION_USES / WARM_REFILL_* |
3 / 4 / β¦ |
Pre-warming & staggering |
RATE_BURST / RATE_REFILL_MS / RATE_MIN_GAP_MS / RATE_JITTER_MS |
4 / 6000 / 1500 / 500 |
Pacing & bucket |
RATE_COOLDOWN_MS / RATE_COOLDOWN_MAX_MS / RATE_MAX_WAIT_MS |
45000 / 300000 / 35000 |
Throttle backoff |
WATCHDOG_MS / WATCHDOG_JITTER_MS |
120000 / 20000 |
Liveness probes |
STREAM_HOLD_BACK |
32 |
Stream tool-call safety buffer |
LOG_REQUESTS |
β | 1 = lightweight logs (no bodies/secrets) |
Full list including signup/captcha/mailbox variables:
.env.exampleand tutos/SETUP.md.
- No refresh token: only login/register/oauth mint a
userTokenβ tokens are replaced, not refreshed. 40003is ambiguous: revocation (re-login may help) vs suspension (all endpoints fail, wait it out) β the proxy distinguishes, backs off, and reports state in/health.- Silent throttling: empty responses after bursts; the proxy paces, waits, and retries (progressive backoff).
- Anti-suspension is not guaranteed: pacing/jitter/gaps, session reuse, staggered pre-warm and watchdog reduce signals but cannot eliminate policy risk.
Warning
Keep volume low and human-like, prefer clean/residential egress when needed, and avoid sustained automated loops.
server.js HTTP server, OpenAI layer, routing, metrics
lib/
deepseek.js Upstream client (challenge/session/completion, proxy tunnel)
pow.js WASM PoW solver
stream.js SSE assembly β fragments
streamgate.js Stream hold-back / tool-call marker detection
tools.js Tool emulation (prompt, JSON/DSML parsing, transcript)
pool.js Token pool, hot-reload, swap/re-login
ratelimit.js Bucket + mutex + pacing/jitter/cooldown
warm.js Pre-warm challenges/sessions
watchdog.js Liveness probe, triggers re-login
auth.js / signup.js Login and signup (experimental)
captcha.js / mailbox.js Captcha solvers & verification-code readers
metrics.js / env.js Metrics and .env loading
scripts/ login, signup, tests, bench, probes
tutos/ Integration guides (OpenCode, Cursor, Cline, Aider, etc.)
run.sh start/restart helper (pidfile)
docker build -t free-deepseek-api .
docker run -p 8787:8787 -e API_KEY=sk-local -v "$PWD/tokens.txt:/app/tokens.txt:ro" free-deepseek-api
# or
docker compose up -d --buildnpm run test:offline # parsers + streamgate + PoW (no network)
npm run test:all # offline + signup gate smoke (network)
node --check server.js lib/*.js scripts/*.jsGLWTS Β· Good luck with that shit. Use responsibly.