13 KiB
DS2API API Reference (Go Implementation)
This document describes the actual behavior of the current Go codebase.
Basics
- Base URL:
http://localhost:5001or your deployment domain - Default content type:
application/json - Health probes:
GET /healthz,GET /readyz
Authentication Rules
Business endpoints (/v1/*, /anthropic/*) accept either:
Authorization: Bearer <token>x-api-key: <token>(withoutBearer)
Admin endpoints:
POST /admin/loginis publicGET /admin/verifyrequiresAuthorization: Bearer <jwt>(JWT only)- Other protected
/admin/*endpoints accept: Authorization: Bearer <jwt>Authorization: Bearer <admin_key>
Route Index
| Method | Path | Description |
|---|---|---|
| GET | /healthz |
Liveness probe |
| GET | /readyz |
Readiness probe |
| GET | /v1/models |
OpenAI model list |
| POST | /v1/chat/completions |
OpenAI chat completions |
| GET | /anthropic/v1/models |
Claude model list |
| POST | /anthropic/v1/messages |
Claude messages |
| POST | /anthropic/v1/messages/count_tokens |
Claude token counting |
| POST | /admin/login |
Admin login |
| GET | /admin/verify |
Verify admin JWT |
| GET | /admin/vercel/config |
Read preconfigured Vercel creds |
| GET | /admin/config |
Read sanitized config |
| POST | /admin/config |
Update config |
| POST | /admin/keys |
Add API key |
| DELETE | /admin/keys/{key} |
Delete API key |
| GET | /admin/accounts |
Paginated account list |
| POST | /admin/accounts |
Add account |
| DELETE | /admin/accounts/{identifier} |
Delete account |
| GET | /admin/queue/status |
Account queue status |
| POST | /admin/accounts/test |
Test one account |
| POST | /admin/accounts/test-all |
Test all accounts |
| POST | /admin/import |
Batch import keys/accounts |
| POST | /admin/test |
Test API through current service |
| POST | /admin/vercel/sync |
Sync config to Vercel |
| GET | /admin/vercel/status |
Vercel sync status |
| GET | /admin/export |
Export config JSON/Base64 |
Health Endpoints
GET /healthz
{"status":"ok"}
GET /readyz
{"status":"ready"}
OpenAI-Compatible API
GET /v1/models
No auth required.
Example response:
{
"object": "list",
"data": [
{"id": "deepseek-chat", "object": "model", "created": 1677610602, "owned_by": "deepseek", "permission": []},
{"id": "deepseek-reasoner", "object": "model", "created": 1677610602, "owned_by": "deepseek", "permission": []},
{"id": "deepseek-chat-search", "object": "model", "created": 1677610602, "owned_by": "deepseek", "permission": []},
{"id": "deepseek-reasoner-search", "object": "model", "created": 1677610602, "owned_by": "deepseek", "permission": []}
]
}
POST /v1/chat/completions
Headers:
Authorization: Bearer your-api-key
Content-Type: application/json
Core request fields:
| Field | Type | Required | Notes |
|---|---|---|---|
model |
string | yes | deepseek-chat / deepseek-reasoner / deepseek-chat-search / deepseek-reasoner-search |
messages |
array | yes | OpenAI-style messages |
stream |
boolean | no | default false |
tools |
array | no | Function calling schema |
temperature, etc. |
any | no | accepted in request; final behavior depends on upstream |
Non-stream example:
{
"id": "<chat_session_id>",
"object": "chat.completion",
"created": 1738400000,
"model": "deepseek-reasoner",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "final response",
"reasoning_content": "reasoning trace"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 20,
"total_tokens": 30,
"completion_tokens_details": {
"reasoning_tokens": 5
}
}
}
OpenAI Streaming (stream=true)
SSE format: each frame is data: <json>\n\n, terminated by data: [DONE].
- First delta may include
role: assistant - Reasoning models emit
delta.reasoning_content - Text emits
delta.content - Last chunk includes
finish_reasonand usage
Example:
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"role":"assistant"},"index":0}]}
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"reasoning_content":"..."},"index":0}]}
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"content":"..."},"index":0}]}
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{},"index":0,"finish_reason":"stop"}],"usage":{...}}
data: [DONE]
Tool Calls (Important)
When tools is present, DS2API injects a tool prompt and parses tool-call payloads.
- Non-stream: if detected, returns
message.tool_calls,finish_reason=tool_calls, andmessage.content=null - Stream: to avoid leaking raw tool-call JSON, DS2API buffers text first; if tool call is detected, only structured
delta.tool_callsis emitted
Tool-call response example:
{
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_xxx",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"beijing\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}
Claude-Compatible API
GET /anthropic/v1/models
No auth required.
Example response:
{
"object": "list",
"data": [
{"id": "claude-sonnet-4-20250514", "object": "model", "created": 1715635200, "owned_by": "anthropic"},
{"id": "claude-sonnet-4-20250514-fast", "object": "model", "created": 1715635200, "owned_by": "anthropic"},
{"id": "claude-sonnet-4-20250514-slow", "object": "model", "created": 1715635200, "owned_by": "anthropic"}
]
}
POST /anthropic/v1/messages
Headers can be:
x-api-key: your-api-key
Content-Type: application/json
anthropic-version: 2023-06-01
Core request fields:
| Field | Type | Required | Notes |
|---|---|---|---|
model |
string | yes | claude-sonnet-4-20250514 / -fast / -slow |
messages |
array | yes | Claude-style messages |
max_tokens |
number | no | currently not strictly enforced by upstream bridge |
stream |
boolean | no | default false |
system |
string | no | optional system prompt |
tools |
array | no | Claude tool schema |
Non-stream example:
{
"id": "msg_1738400000000000000",
"type": "message",
"role": "assistant",
"model": "claude-sonnet-4-20250514",
"content": [
{"type": "text", "text": "response"}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 12,
"output_tokens": 34
}
}
If tool use is detected, stop_reason becomes tool_use and content contains tool_use blocks.
Claude Streaming (stream=true)
Still SSE, but current implementation writes data: lines only (no event: lines). Event type is carried in JSON type.
Example:
data: {"type":"message_start","message":{...}}
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"hello"}}
data: {"type":"content_block_stop","index":0}
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":12}}
data: {"type":"message_stop"}
POST /anthropic/v1/messages/count_tokens
Request example:
{
"model": "claude-sonnet-4-20250514",
"messages": [
{"role": "user", "content": "Hello"}
]
}
Response example:
{
"input_tokens": 5
}
Admin API
POST /admin/login
Request:
{
"admin_key": "admin",
"expire_hours": 24
}
expire_hours is optional, default 24.
Response:
{
"success": true,
"token": "<jwt>",
"expires_in": 86400
}
GET /admin/verify
Header: Authorization: Bearer <jwt>
Response:
{
"valid": true,
"expires_at": 1738400000,
"remaining_seconds": 72000
}
GET /admin/vercel/config
{
"has_token": true,
"project_id": "prj_xxx",
"team_id": null
}
GET /admin/config
Sanitized config response:
{
"keys": ["k1", "k2"],
"accounts": [
{
"email": "user@example.com",
"mobile": "",
"has_password": true,
"has_token": true,
"token_preview": "abcde..."
}
],
"claude_mapping": {
"fast": "deepseek-chat",
"slow": "deepseek-reasoner"
}
}
POST /admin/config
Updatable fields: keys, accounts, claude_mapping.
Request example:
{
"keys": ["k1", "k2"],
"accounts": [
{"email": "user@example.com", "password": "pwd", "token": ""}
],
"claude_mapping": {
"fast": "deepseek-chat",
"slow": "deepseek-reasoner"
}
}
POST /admin/keys
{"key":"new-api-key"}
Response:
{"success":true,"total_keys":3}
DELETE /admin/keys/{key}
{"success":true,"total_keys":2}
GET /admin/accounts
Query params:
page(default 1)page_size(default 10, max 100)
Response:
{
"items": [
{
"email": "user@example.com",
"mobile": "",
"has_password": true,
"has_token": true,
"token_preview": "abc..."
}
],
"total": 25,
"page": 1,
"page_size": 10,
"total_pages": 3
}
POST /admin/accounts
{"email":"user@example.com","password":"pwd"}
{"success":true,"total_accounts":6}
DELETE /admin/accounts/{identifier}
identifier is email or mobile.
{"success":true,"total_accounts":5}
GET /admin/queue/status
{
"available": 3,
"in_use": 1,
"total": 4,
"available_accounts": ["a@example.com"],
"in_use_accounts": ["b@example.com"]
}
POST /admin/accounts/test
Request fields:
| Field | Required | Notes |
|---|---|---|
identifier |
yes | email or mobile |
model |
no | default deepseek-chat |
message |
no | if empty, only session creation is tested |
Response example:
{
"account": "user@example.com",
"success": true,
"response_time": 1240,
"message": "API 测试成功(仅会话创建)",
"model": "deepseek-chat"
}
POST /admin/accounts/test-all
Optional request field: model.
{
"total": 5,
"success": 4,
"failed": 1,
"results": []
}
POST /admin/import
{
"keys": ["k1", "k2"],
"accounts": [
{"email":"user@example.com","password":"pwd","token":""}
]
}
{
"success": true,
"imported_keys": 2,
"imported_accounts": 1
}
POST /admin/test
Optional request fields:
model(defaultdeepseek-chat)message(default你好)api_key(default first key in config)
Response example:
{
"success": true,
"status_code": 200,
"response": {"id":"..."}
}
POST /admin/vercel/sync
Request fields:
| Field | Required | Notes |
|---|---|---|
vercel_token |
no | if empty or __USE_PRECONFIG__, read env |
project_id |
no | fallback: VERCEL_PROJECT_ID |
team_id |
no | fallback: VERCEL_TEAM_ID |
auto_validate |
no | default true |
save_credentials |
no | default true |
Success response example:
{
"success": true,
"validated_accounts": 3,
"message": "配置已同步,正在重新部署...",
"deployment_url": "https://..."
}
Or manual deploy required:
{
"success": true,
"validated_accounts": 3,
"message": "配置已同步到 Vercel,请手动触发重新部署",
"manual_deploy_required": true
}
GET /admin/vercel/status
{
"synced": true,
"last_sync_time": 1738400000,
"has_synced_before": true
}
GET /admin/export
{
"json": "{...}",
"base64": "ey4uLn0="
}
Error Payloads
Error payload formats are not fully unified in current code:
- OpenAI routes often return:
{"error":"..."} - Claude routes often return:
{"error":{"type":"...","message":"..."}} - Admin routes often return:
{"detail":"..."}
Clients should handle HTTP status code plus error / detail fields.
cURL Examples
OpenAI non-stream
curl http://localhost:5001/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "Hello"}],
"stream": false
}'
OpenAI stream
curl http://localhost:5001/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-reasoner",
"messages": [{"role": "user", "content": "Explain quantum entanglement"}],
"stream": true
}'
Claude
curl http://localhost:5001/anthropic/v1/messages \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-sonnet-4-20250514",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello"}]
}'