API reference
Request and response schemas plus curl examples for the four chat protocol endpoints, GET /v1/models and the balance/billing endpoints, with an index of the media, digital-human and knowledge-base endpoints
Every public endpoint on the platform accepts the same sk-gpushare-* API key. This page details the four chat protocol endpoints plus GET /v1/models and both sets of balance endpoints; the media (image, video, music, speech), digital-human and knowledge-base endpoints have pages of their own.
| Endpoint | Protocol / purpose | Details |
|---|---|---|
POST /v1/chat/completions | OpenAI Chat — the most universal, works across vendors | this page |
POST /v1/messages | Anthropic Messages — for the Anthropic SDK directly | this page |
POST /v1beta/models/{model}:generateContent | Gemini Native — for Google's genai SDK directly | this page |
POST /v1/responses | OpenAI Responses — GPT-5.x's native protocol, with built-in tools | this page |
GET /v1/models | OpenAI model discovery (SDKs and third-party clients call it automatically) | this page |
GET /v1/key/balance | This key's remaining, used and total credit | this page |
GET /v1/dashboard/billing/* | OpenAI's own billing endpoints — what third-party clients call to show a balance | this page |
POST /v1/images/generations | Text-to-image and image-to-image (synchronous), billed per image | Image / video / music APIs |
POST /v1/videos/generations (plus GET .../{id} polling and a GET list) | Text-to-video and image-to-video (async task), billed per second | Image / video / music APIs |
POST /v1/music/generations (plus GET .../{id} polling) | AI music generation (async task), billed per generation | Image / video / music APIs |
POST /v1/audio/speech / /v1/audio/voices | Speech synthesis (per character) and voice cloning (per call) | Image / video / music APIs |
POST /v1/videos/avatars + GET /v1/videos/clip-templates | The avatar library and smart-edit templates | Digital human / smart edit API |
POST /v1/embeddings | ⚠️ The embedding SKUs were withdrawn in July 2026, so no model is currently available (calls return 404 model_not_found) | — |
POST /v1/transcripts/extract | Short-video link → spoken script (synchronous), billed per call | Image / video / music APIs |
/api/v1/ext/wiki/* (8 read-only REST endpoints) + /mcp (an MCP server) | Knowledge-base search | Knowledge base API & MCP |
Base URL: https://dianqi.zsopc.com
Authentication#
See Authentication for the full story. Pick any of the four forms; they fall back in this order:
- the
x-api-key: sk-gpushare-xxxheader (recommended) - the
x-goog-api-key: sk-gpushare-xxxheader (Google'sgenaiSDK default — Gemini SDK users need change nothing) ?key=sk-gpushare-xxxquery- the
Authorization: Bearer sk-gpushare-xxxheader (the OpenAI and Anthropic SDK default)
One exception:
GET /v1/modelsaccepts only theAuthorization: Bearerandx-api-keyheaders and does not support the?key=query.
POST /v1/chat/completions#
The OpenAI Chat Completions-compatible endpoint. The most universal one, covering every chat model (70+); for the full list see Models or GET /v1/models.
Image- and video-only SKUs (Seedream, Seedance and so on) do not live here — calling them returns 503
no_channel_available. Use their own endpoints instead; see Image / video APIs.
The request#
{
"model": "claude-sonnet-4-6",
"messages": [
{"role": "system", "content": "You are helpful."},
{"role": "user", "content": "Hello"}
],
"stream": false,
"max_tokens": 1024,
"temperature": 0.7,
"tools": [
{"type": "function", "function": {...}}
]
}
| Field | Required | Type | Notes |
|---|---|---|---|
model | ✓ | string | the model ID; see Models |
messages | ✓ | array | conversation history, with role ∈ {system, user, assistant, tool} |
stream | bool | true turns on SSE streaming | |
max_tokens | int | the cap on generated tokens | |
temperature | float | 0-2 | |
tools | array | function tools, or the built-in {type:"web_search"} / {type:"image_generation"} (constraints below) | |
tool_choice | string|object | auto / none / {type:"function","function":{...}} | |
stream_options | object | while streaming, {"include_usage": true} puts token counts in the trailing chunk | |
response_format | object | {"type":"json_object"} forces JSON output |
Constraints on the built-in tools (
{type:"web_search"}and{type:"image_generation"}):
- Both run through the WebSocket V2 adapter on this endpoint, so
stream: trueis mandatory — non-streaming returns 400invalid_requeststraight away (message:Tools `web_search` and `image_generation` require `stream: true`)image_generationis supported only on GPT-5.x (the x1 source); other models return 400tool_not_supportedweb_searchis gated on what each model and channel supports, and an unsupported combination returns 400tool_not_supported. Per-model support is in ModelsFunction tools (
{type:"function",...}) are exempt from all of the above and work either streaming or not.
The response (non-streaming)#
{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1715845200,
"model": "claude-sonnet-4-6",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "Hello!"},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 5,
"total_tokens": 15
}
}
The response (streaming)#
With stream: true you get an SSE stream where each data: line is one chunk. See Streaming.
curl#
curl https://dianqi.zsopc.com/v1/chat/completions \
-H "Authorization: Bearer $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.4",
"messages": [{"role": "user", "content": "Hello"}]
}'
POST /v1/messages#
The Anthropic Messages-compatible endpoint.
The request#
{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"system": "You are helpful.",
"messages": [
{"role": "user", "content": "Hello"}
],
"stream": false,
"tools": [...]
}
| Field | Required | Type | Notes |
|---|---|---|---|
model | ✓ | string | the model ID |
max_tokens | ✓ | int | required by the Anthropic protocol (unlike OpenAI) |
messages | ✓ | array | the conversation, with role ∈ {user, assistant} |
system | string | the system prompt (a top-level field, not part of messages) | |
stream | bool | ||
tools | array | Anthropic's tool format (name / description / input_schema) | |
tool_choice | object | {"type":"auto"|"any"|"tool", "name": "..."} |
The response (non-streaming)#
{
"id": "msg_...",
"type": "message",
"role": "assistant",
"model": "claude-sonnet-4-6",
"content": [
{"type": "text", "text": "Hello!"}
],
"stop_reason": "end_turn",
"usage": {"input_tokens": 10, "output_tokens": 5}
}
curl#
curl https://dianqi.zsopc.com/v1/messages \
-H "x-api-key: $PLATFORM_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello"}]
}'
Limitations#
- For the mainstream
gemini-2.5andgemini-3.xSKUs, prefer the Gemini Native endpoint; somegemini-*SKUs are wired up here too. The exact model × endpoint support is in the compatibility matrix - When the model exists but no channel serves it on this endpoint, you get 503
no_channel_available - The SDK injects the
anthropic-versionheader for you; with raw curl, send2023-06-01
Gemini Native#
Two related endpoints:
POST /v1beta/models/{model}:generateContent— non-streamingPOST /v1beta/models/{model}:streamGenerateContent— streaming
The {model} placeholder goes straight into the URL, e.g. /v1beta/models/gemini-2.5-pro:generateContent.
The request#
{
"contents": [
{"role": "user", "parts": [{"text": "Hello"}]}
],
"systemInstruction": {
"parts": [{"text": "You are helpful."}]
},
"generationConfig": {
"maxOutputTokens": 1024,
"temperature": 0.7
},
"tools": [...]
}
The response#
{
"candidates": [{
"content": {
"role": "model",
"parts": [{"text": "Hello!"}]
},
"finishReason": "STOP",
"index": 0
}],
"usageMetadata": {
"promptTokenCount": 10,
"candidatesTokenCount": 5,
"totalTokenCount": 15
}
}
curl#
curl "https://dianqi.zsopc.com/v1beta/models/gemini-2.5-pro:generateContent?key=$PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contents": [{"parts": [{"text": "Hello"}]}]
}'
Limitations#
- GPT-5.x isn't supported here yet (the model exists but has no channel, so you get 503
no_channel_available); the exact matrix is in the compatibility matrix
POST /v1/responses#
The OpenAI Responses API-compatible endpoint — GPT-5.x's native protocol. Compared with /v1/chat/completions it adds two native tools, web_search and image_generation (drawing with GPT-image-2), and its responses carry full structured reasoning, output_text and tool-call fields.
Supported models#
The GPT-5 family (natively) plus the Claude SKUs on the x1 source; the exact matrix is in the compatibility matrix:
| Model | Notes |
|---|---|
gpt-5.4 | |
gpt-5.5 | recommended — supports reasoning summaries and the built-in tools |
claude-opus-4-6 / claude-opus-4-7 / claude-opus-4-8 | X1 |
claude-sonnet-4-6 | X1 |
claude-haiku-4-5-20251001 | X1 |
What happens when you call something else:
- a model id that isn't in the catalogue → 400
model_not_found - the model exists but no channel serves it here → 503
no_channel_available(use/v1/chat/completionsor that model's native endpoint instead) model_not_allowedappears only when your key has anallowed_modelsallowlist that omits the model
The request#
{
"model": "gpt-5.5",
"instructions": "You are concise.",
"input": [
{"role": "user", "content": "Say hello in 5 words."}
],
"stream": false,
"max_output_tokens": 1024,
"temperature": 1.0,
"top_p": 0.98,
"reasoning": {"effort": "medium"},
"tools": [
{"type": "web_search"},
{"type": "image_generation", "size": "1024x1024"},
{"type": "function", "name": "get_weather", "description": "...", "parameters": {...}}
],
"tool_choice": "auto",
"parallel_tool_calls": true
}
| Field | Required | Type | Notes |
|---|---|---|---|
model | ✓ | string | see Supported models |
input | ✓ | array | string | always use the message array form (below). The GPT-5.x upstream requires input to be an array and rejects the string form |
instructions | string | the system prompt. A top-level field, not messages[0] (unlike /v1/chat/completions). The GPT-5.x upstream requires it, so always send it | |
stream | bool | true returns an SSE event stream | |
max_output_tokens | int | the generation cap (the Responses API uses _output_, not max_tokens) | |
reasoning | object | {"effort": "low"|"medium"|"high"} controls how hard the model thinks internally | |
temperature / top_p / frequency_penalty / presence_penalty | float | the standard sampling parameters | |
tools | array | see Tools | |
tool_choice | string | object | auto / none / {type:"function","name":"..."} | |
parallel_tool_calls | bool | true by default |
The input array form (multi-turn)#
"input": [
{"role": "user", "content": "What is 2+2?"},
{"role": "assistant", "content": "4"},
{"role": "user", "content": "What was my first question?"}
]
An element's content may be shortened to a plain string; vision requires content parts:
{"role": "user", "content": [
{"type": "input_text", "text": "Describe this image."},
{"type": "input_image", "image_url": "https://...", "detail": "auto"}
]}
Note:
input_image.image_urlmust be a publicly reachable URL (the upstream fetches it itself); CDN thumbnails and authenticated URLs may come back asupstream_error.
Tools#
// 1. built-in web search — the model decides whether to use it and returns output_text with the answer
{"type": "web_search"}
// 2. built-in image generation — output carries an image_generation_call item (with a base64 result)
{"type": "image_generation", "size": "1024x1024"}
// 3. your own function — output carries a function_call item; you run it and send back function_call_output
{
"type": "function",
"name": "get_weather",
"description": "Get current weather",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
}
The response (non-streaming)#
{
"id": "resp_0a430185e6bd1abb016a1576c7bbb08198be6868b655d19349",
"object": "response",
"created_at": 1779791559,
"status": "completed",
"model": "gpt-5.5",
"instructions": "...",
"output": [
{
"type": "message",
"role": "assistant",
"content": [{"type": "output_text", "text": "Hello, hope you are well."}]
}
],
"reasoning": {"context": "current_turn", "effort": "medium", "summary": null},
"usage": {
"input_tokens": 25,
"input_tokens_details": {"cached_tokens": 0},
"output_tokens": 51,
"output_tokens_details": {"reasoning_tokens": 38},
"total_tokens": 76
}
}
The output[] array is in the order things happened, and each item's type is one of:
type | Meaning |
|---|---|
message | the assistant's text reply, in content[].text |
reasoning | a summary of the internal reasoning (may be empty) |
function_call | the model chose to call your function; fields are call_id, name and arguments (a JSON string) |
image_generation_call | the result of the built-in image_generation tool; result is a base64 PNG |
The response (streaming)#
With stream: true you get SSE whose event names are prefixed response.. The key sequence:
event: response.created // the overall response frame (usage=null)
event: response.in_progress
event: response.output_item.added // output item N begins
event: response.output_text.delta // an incremental piece of text; the chunk is in delta
event: response.output_text.done // item N's text is complete
event: response.output_item.done // item N is finished
event: response.completed // everything is done and usage is populated
Every data: is a single JSON object carrying a monotonically increasing sequence_number. See Streaming.
curl#
# a basic call
curl https://dianqi.zsopc.com/v1/responses \
-H "Authorization: Bearer $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.5",
"instructions": "You are helpful.",
"input": [{"role": "user", "content": "Hello"}],
"max_output_tokens": 100
}'
# web search
curl https://dianqi.zsopc.com/v1/responses \
-H "Authorization: Bearer $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.5",
"instructions": "You are helpful.",
"input": [{"role": "user", "content": "What is today date in Shanghai?"}],
"tools": [{"type": "web_search"}]
}'
# streaming
curl -N https://dianqi.zsopc.com/v1/responses \
-H "Authorization: Bearer $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.5",
"instructions": "You are helpful.",
"input": [{"role": "user", "content": "Count 1 to 3."}],
"stream": true
}'
Limitations#
- Multi-turn conversations must maintain their own history in the
inputarray. The upstream doesn't persist conversation items (store: falsesemantics) andprevious_response_idis unavailable — put the full history ininputevery turn - The model range is under Supported models; anything else belongs on
/v1/chat/completionsor its own native endpoint (error semantics above) - Vision is limited by what the upstream can fetch — some CDN thumbnails, authenticated URLs and hotlink-protected URLs fail. Upload the image to your own public R2 / S3 bucket and pass that URL
- The
image_generationtool doesn't force streaming here — this endpoint is an HTTP pass-through, so even withstream:falseyou findimage_generation_call.result(a base64 PNG) inoutput[]. That differs from/v1/chat/completions, whereimage_generationruns through a dedicated WebSocket streaming adapter that forcesstream:trueand embeds the image as markdown insidedelta.content
GET /v1/models#
The OpenAI-compatible model discovery endpoint. It's what the OpenAI SDK's client.models.list() calls, and what most third-party clients (Open WebUI, Cline, Continue and so on) use to discover models automatically.
An authentication difference#
This endpoint accepts only two header forms and does not support the ?key= query — unlike the four-way fallback elsewhere, a Gemini-style query call gets a 401:
Authorization: Bearer sk-gpushare-xxxx-api-key: sk-gpushare-xxx
The response#
{
"object": "list",
"data": [
{"id": "claude-sonnet-4-6", "object": "model", "created": 0, "owned_by": "anthropic"},
{"id": "gpt-5.5", "object": "model", "created": 0, "owned_by": "openai"}
]
}
- It returns every entry in the pricing registry, including image-, video- and embedding-only SKUs and placeholder SKUs that aren't callable yet — filter as needed when building a model menu or scripting against it
- If your key has an
allowed_modelsallowlist, only allowlisted entries come back - There's also an unauthenticated
GET /api/v1/models/publicreturning the whole registry with pricing and capability fields (including thecallableflag andendpoint_type), handy for comparison and filtering
curl#
curl https://dianqi.zsopc.com/v1/models \
-H "Authorization: Bearer $PLATFORM_API_KEY"
GET /v1/key/balance#
Check the balance of the account behind this key. Free, and not rate-billed.
It still answers normally at a zero balance (where every other billing endpoint would 402; GET /v1/models likewise doesn't check the balance) — which is exactly the point: monitor with it before you run dry.
curl https://dianqi.zsopc.com/v1/key/balance \
-H "Authorization: Bearer $PLATFORM_API_KEY"
{
"object": "key.balance",
"remaining_usd": "12.3456",
"used_usd": "7.6544",
"total_usd": "20.0000",
"expires_at": null
}
| Field | Meaning |
|---|---|
remaining_usd | How much this key can actually still spend (floored at 0): the lesser of the account balance and whatever remains of the key's spend cap |
used_usd | This key's lifetime spend. Keys with a spend cap report the cap's counter; uncapped keys report the total across this key's own call log — either way it only ever counts this one key, never the account's or an enterprise owner's pooled spend |
total_usd | remaining_usd + used_usd. A prepaid wallet has no "granted credit" of its own; this is the synthetic denominator the billing endpoints below publish |
expires_at | When the key expires; null means never |
The balance itself is shared by every key on the account rather than being per-key.
⚠️ When a key has no spend cap,
remaining_usdis the backing wallet's balance — for an enterprise-issued key, that's the owner's pool. Only a key with a spend cap reports "what this key has left" instead of the whole pool. If you don't want the holder to see the pool balance, give that key a spend cap (see key spend limits).
Amounts are JSON strings (so no float precision is lost) — parseFloat them client-side.
Authentication matches every other endpoint (any of the four forms). This endpoint does not update the key's "last used" time, so polling it regularly won't register as traffic. used_usd may lag by up to 60 seconds; remaining_usd is always live.
GET /v1/dashboard/billing#
GET /v1/dashboard/billing/subscription
GET /v1/dashboard/billing/usage
/v1/key/balance above is a name this platform invented, and no third-party client knows it. These two are OpenAI's own billing endpoints: relay platforms like New API / One API call them behind their "update balance" button, so pasting the key in is enough with no extra configuration. Many desktop clients (Cherry Studio, ChatBox, NextChat, LobeChat and others) use the same pair for their "check balance" feature, though that depends on your client's version.
GET /v1/dashboard/billing/credit_grants(OpenAI's other legacy billing endpoint) is not implemented — older clients that call it get a 404. Use the two above.
Both are mounted with and without the /v1 prefix (clients differ on how they join the base URL) and return identical payloads.
curl https://dianqi.zsopc.com/v1/dashboard/billing/subscription \
-H "Authorization: Bearer $PLATFORM_API_KEY"
{
"object": "billing_subscription",
"has_payment_method": true,
"soft_limit_usd": 20.0,
"hard_limit_usd": 20.0,
"system_hard_limit_usd": 20.0,
"access_until": 0
}
curl "https://dianqi.zsopc.com/v1/dashboard/billing/usage?start_date=2026-07-01&end_date=2026-08-01" \
-H "Authorization: Bearer $PLATFORM_API_KEY"
{
"object": "list",
"total_usage": 765.44,
"daily_costs": []
}
- How these map to
/v1/key/balance:hard_limit_usd=total_usd, andtotal_usage=used_usd× 100 (total_usageis in cents, OpenAI's original unit). The balance a client derives ashard_limit_usd - total_usage / 100is therefore always exactlyremaining_usd. - Amounts on these two endpoints are JSON numbers, not strings — that's the official schema, and clients parse them as floats.
access_untilis the key's expiry as Unix seconds, or0when it never expires.start_date/end_dateare accepted and ignored;total_usageis always the lifetime figure. The reason: thesubscriptionendpoint receives no date window at all, so a windowedtotal_usagewould make the client's subtraction report a wrong balance. New API / One API ignore them for the same reason.daily_costsis always an empty array. For per-day or per-model detail, use the usage pages in the console.- Same properties as
/v1/key/balance: they answer 200 at a zero balance, and they are not billed, not rate-limited, and don't update the key's "last used" time.
For step-by-step client and relay-platform setup, see New API and third-party clients.
Error responses#
Whenever an endpoint returns HTTP 4xx/5xx, the body uses that protocol's official error schema — never a mixture:
OpenAI shape (/v1/chat/completions, /v1/responses, /v1/embeddings)#
{"error": {"message": "...", "type": "...", "code": "..."}}
Anthropic Messages (/v1/messages)#
{"type": "error", "error": {"type": "...", "message": "..."}}
Gemini Native (/v1beta/...)#
{"error": {"code": 400, "message": "...", "status": "INVALID_ARGUMENT"}}
Errors mid-stream: once a streaming request has opened (HTTP 200 is already out), a later upstream error cannot change the status code — it shows up as a truncated stream or an SSE error frame, so your client needs a fallback for "the stream never ended cleanly". See Error codes.
The most common error is an exhausted balance: HTTP 402, which in the OpenAI shape is code: "quota_exceeded" / type: "insufficient_quota" (Anthropic: type: "billing_error"; Gemini: status: "RESOURCE_EXHAUSTED"). The full truth table is in Error codes.
Limits and timeouts#
Rate limits#
The platform sets two ceilings per account (shared by all of that account's API keys, not one set each):
| Dimension | Meaning |
|---|---|
| Requests per minute | requests in the last 60 seconds (a sliding window) |
| Max concurrency | requests in flight (not yet finished) at any one moment |
Unlimited by default; the platform configures them per account when needed, and can loosen or tighten any single account. Every /v1/* request counts, including status polling for async tasks (video, music, digital human) — if your integration polls heavily, widen the interval or ask us to raise the ceiling.
Hitting a limit returns 429 rate_limit_exceeded with these headers:
| Header | Meaning |
|---|---|
Retry-After | how many seconds to wait (just do as it says) |
x-ratelimit-limit-requests / -remaining-requests / -reset-requests | the per-minute request ceiling, what's left, and seconds until reset |
x-ratelimit-limit-concurrency / -remaining-concurrency | the concurrency ceiling and what's left |
When limits are in place, successful responses carry the x-ratelimit-* headers too, so clients can throttle themselves adaptively. You can look up your account's ceilings at dianqi.zsopc.com/dashboard/keys (the section is hidden for accounts with no limits set).
A
429may also be an upstream rate limit passed through, unrelated to our own. Tell them apart by the headers: withx-ratelimit-*it's ours, without it's the upstream's — either way, retry with exponential backoff.
Billing and balance#
Charges come out of a single account balance (a USD wallet) shared by every API key — no key has its own budget pool, and when the balance runs out every key returns 402 at once (creating a new key doesn't help). Sign-up includes 121.32 of trial credit; top up at dflop.top/dashboard/billing (the same account as dianqi.zsopc.com). See Authentication.
Timeouts#
| Endpoint | Upstream timeout |
|---|---|
The four chat protocol endpoints (/v1/chat/completions, /v1/messages, /v1/responses, /v1beta/...) | 180s in total (streaming is bound by the same total — it just gets you the first token sooner, it isn't unbounded) |
POST /v1/images/generations | 240s per attempt / 280s across the ladder (set client ≥300s) |
POST /v1/videos/generations | 60s (submission only; generation is async and doesn't hold the request) |
POST /v1/embeddings | 30s |
POST /v1/transcripts/extract | ~55s (synchronously blocking: it creates a task internally and polls upstream to completion) |
Set your SDK timeout to ≥ 200 seconds (long reasoning or tool-calling turns can run to minutes — prefer streaming). Very long jobs such as video use async polling; see Image / video / music APIs.
Automatic failover and retries#
On an upstream 5xx or connection failure the gateway switches channels and retries, trying at most 3 channels per request — which is why one request occasionally takes twice as long. A 502 upstream_unreachable or 504 upstream_timeout is safe for your client to retry.