How it works
Reverse-engineered provider protocols and account limits.
DeepSeek
Protocol reverse-engineered from the chat.deepseek.com main bundle and its sha3 wasm module:
- Auth:
POST /api/v0/users/login→data.biz_data.user.token, thenAuthorization: Bearer <token>. - Headers:
x-client-bundle-id,x-client-platform,x-client-version,x-client-locale,x-client-timezone-offset. - Session:
POST /api/v0/chat_session/create(empty body) →chat_session.id. - Generation:
POST /api/v0/chat/completion:{chat_session_id, parent_message_id, model_type, prompt, ref_file_ids, thinking_enabled, search_enabled, action, preempt}. - Response -
text/event-stream:readyevents, deltas (SET/APPEND/BATCH,response/...paths),finish,close. -
PoW header
X-DS-PoW-Response- base64 of{algorithm, challenge, salt, answer, signature, target_path}. The challenge is single-use:answer= minimal counter c whereDeepSeekHashV1(f"{salt}_{expire_at}_" + str(c))matcheschallenge(32 bytes). The server iterates c in[0, difficulty).
Qwen
Protocol reverse-engineered from the chat.qwen.ai frontend bundle:
- Auth:
POST /api/v2/auths/signinwith{email, password}where the password is SHA-256 hex of the plain text →data.token(JWT). Requests send it asAuthorization: Bearer <token>and thetokencookie. - Headers:
source: web,version,X-Request-Id,Timezone, browsersec-ch-ua/User-Agent/Origin/Referer. - Session:
POST /api/v2/chats/new({chatId, models, chat_type: "t2t", chat_mode: "normal", timestamp}) →data.id(the chat id). -
Generation:
POST /api/v2/chat/completions?chat_id=<id>with{stream, version: "2.1", incremental_output, chat_id, model, parent_id, messages: [{fid, parentId, role, content, chat_type: "t2t", feature_config: {thinking_enabled, output_schema: "phase", ...}}]}. The chat history lives server-side;parent_idpoints at the last assistant response id, so the next turn continues the same conversation. -
Response -
text/event-streamof OpenAI-style JSON chunks:{"choices": [{"delta": {"role", "content", "phase", "status"}}], "response_id", "usage"}. Theresponse.createdchunk opens the stream with the assistantresponse_id; content is streamed in theanswerphase, thinking inthink/DeepThinking/thinking_summaryphases, and the stream ends with a chunk whosedelta.statusisfinished.
Account limits
-
One chat.deepseek.com account can generate one message at a time
(otherwise the server replies
parallel_chat_limit). DanyAPI keeps an account pool and distributes concurrent requests across accounts; if all are busy, requests wait in a queue. More tokens = more parallel generations. The same applies to chat.qwen.ai accounts (Qwen uses its own pool, so DeepSeek and Qwen parallel generations are independent). -
Sessions are tied to the account they were created on: repeat requests with
the same
session_id(or the same cached message context) route to the same account, so the conversation history stays intact server-side. -
The in-memory session/context cache is LRU-bounded per account and per
provider. When an entry is evicted, the corresponding chat is no longer
reused and a new one is created on the next request; the explicit
session_idremains the reliable way to pin a conversation. Unused entries also expire after DANYAPI_SESSION_TTL_SECONDS, and session-id → account affinity is cleaned up together with the cache, so memory stays bounded on long-running instances. -
DeepSeek may throttle accounts (especially the expert model deepseek-v4-pro - "limited resource"). Responses with
finish_reasonexpert_busy_use_default/parallel_chat_limit/server_busy/busyare retried automatically (up to 5 retries with exponential backoff). If all attempts are exhausted:- non-stream requests get HTTP 429 with the DeepSeek error text;
- stream requests get an SSE
errorevent withfinish_reason.
-
Qwen may reply
Too_Many_Requests/RateLimited/quotaLimited; those are retried automatically the same way (up to 5 retries), then reported as HTTP 429 or an SSEerrorevent. - The DeepSeek PoW challenge is single-use - a new one is solved per request (the next one is prefetched in advance so you don't wait).
- When all accounts are busy, requests wait for a free account. Set DANYAPI_ACQUIRE_TIMEOUT (seconds) to cap that wait and get an HTTP 429 ("all accounts are busy") instead of waiting forever.
-
Long server-side conversations eventually exceed the model's context window.
When that happens DanyAPI detects the context-limit error, discards the
overflowing chat (so it is never reused) and reports the failure: HTTP 400
for non-stream requests, or an SSE
errorevent withfinish_reason: "length"for stream requests. The next request automatically starts with a fresh conversation. To avoid hitting the limit often, keep Qwen built-in tools disabled (see Account setup) and rotate long conversations client-side.