Documentation
Flashline is a focused, OpenAI-compatible gateway for deepseek-v4.1-flash and accountless glm-5.3-flash. One request shape, independent provider queues, and a ledger that tells you exactly what each call cost.
Quickstart
Create an account, then create an API key in the dashboard. The base URL is:
__BASE_URL__Send the key as a bearer token and include the OpenCode client headers. The canonical model IDs are deepseek-v4.1-flash and glm-5.3-flash.
curl __BASE_URL__/chat/completions \
-H "Authorization: Bearer $FLASHLINE_KEY" \
-H "X-Title: opencode" \
-H "X-Flashline-Client: opencode" \
-H "X-Flashline-Client-Token: $FLASHLINE_CLIENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4.1-flash",
"messages": [
{ "role": "user", "content": "Explain SSE in one sentence." }
]
}'
You can also use x-api-key: $FLASHLINE_KEY. The gateway accepts the OpenAI chat-completions and responses request shapes. Change "model" to "glm-5.3-flash" to use the accountless guest GLM path; it supports "reasoning_effort": "low" | "high" | "max".
Authentication
Every /v1/* request needs a valid key and the OpenCode client headers. Dashboard keys start with sk-fl-, are billed against your prepaid balance, and can be revoked individually. The operator key is separate and unbilled; it is intended for internal tooling and the OpenCode provider.
403 opencode_client_required, even when the API key is valid. The client token is issued by the operator and must be kept private.| Header | Value |
|---|---|
Authorization | Bearer sk-fl-… |
x-api-key | sk-fl-… (alternative) |
X-Title | opencode |
X-Flashline-Client | opencode |
X-Flashline-Client-Token | Client token from the operator |
Content-Type | application/json |
Configure the same headers in the OpenCode provider:
"deepseek-web": {
"headers": {
"X-Title": "opencode",
"X-Flashline-Client": "opencode",
"X-Flashline-Client-Token": "<client token>",
"User-Agent": "opencode/2.0.18"
}
}
Models
deepseek-v4.1-flash uses the exported DeepSeek Web account. glm-5.3-flash uses the z.ai guest Web protocol, is browserless and accountless, and starts in thinking mode with reasoning_effort: "max". Guest GLM supports only the Flash model; glm-5.3 and glm-5.2 require a logged-in z.ai browser session and are not served. Pro, expert, and unknown IDs are rejected with 400 model_not_found instead of being silently remapped.
curl __BASE_URL__/models \ -H "Authorization: Bearer $FLASHLINE_KEY" \ -H "X-Title: opencode" \ -H "X-Flashline-Client: opencode" \ -H "X-Flashline-Client-Token: $FLASHLINE_CLIENT_TOKEN"
{
"object": "list",
"data": [
{ "id": "deepseek-v4.1-flash", "object": "model", "owned_by": "deepseek-web" },
{
"id": "glm-5.3-flash",
"object": "model",
"owned_by": "z.ai-web",
"metadata": {
"provider": "zchat.z.ai",
"thinking_effort_levels": ["low", "high", "max"],
"default_thinking_effort": "max"
}
}
]
}
Chat completions
POST /v1/chat/completions accepts an OpenAI-style messages array. Use the model ID exactly as listed.
| Field | Type | Notes |
|---|---|---|
model | string | Required in practice; use deepseek-v4.1-flash or glm-5.3-flash. |
messages | array | system, user, and assistant roles. Text content is supported. |
stream | boolean | Set true for SSE chunks. |
thinking | boolean | Thinking is on by default. Set false to disable it. |
web_search | boolean | Requests upstream web search. A per-request search fee applies. |
reasoning_effort | string | GLM only: low, high, or max (default). |
previous_response_id | string | Continue a conversation returned by a previous call. |
conversation | string | Explicit conversation/session ID. Must belong to the same account. |
Non-streaming responses include an OpenAI-compatible message plus a usage object. For billed keys, usage.cost_usd and usage.balance_usd are included, along with a billing receipt.
{
"id": "chatcmpl_…",
"object": "chat.completion",
"model": "deepseek-v4.1-flash",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "…" },
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 28,
"completion_tokens": 64,
"total_tokens": 92,
"cost_usd": 0.0000426,
"balance_usd": 0.2499574
},
"billing": {
"costUsd": 0.0000426,
"peak": false,
"phase": "off-peak",
"legacy": false
},
"conversation": "…",
"previous_response_id": "resp_…"
}
Responses
POST /v1/responses accepts a string input or an array of message-like items, plus optional instructions. It returns a Responses-style object with output, output_text, usage, and the same billing receipt when the key is metered.
curl __BASE_URL__/responses \
-H "Authorization: Bearer $FLASHLINE_KEY" \
-H "X-Title: opencode" \
-H "X-Flashline-Client: opencode" \
-H "X-Flashline-Client-Token: $FLASHLINE_CLIENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4.1-flash",
"instructions": "Answer in one short paragraph.",
"input": "What is a token, really?"
}'
Set stream: true to receive Responses-style SSE events, ending with a response.usage event and data: [DONE].
Streaming
Chat streaming emits chat.completion.chunk events. Reasoning text arrives as delta.reasoning_content; answer text arrives as delta.content. The final chunk carries finish_reason: "stop" and a usage object, followed by data: [DONE].
curl -N __BASE_URL__/chat/completions \
-H "Authorization: Bearer $FLASHLINE_KEY" \
-H "X-Title: opencode" \
-H "X-Flashline-Client: opencode" \
-H "X-Flashline-Client-Token: $FLASHLINE_CLIENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4.1-flash",
"messages": [{ "role": "user", "content": "Write a haiku about latency." }],
"stream": true
}'
Cache-Control: no-cache and keep the connection open until [DONE]. The gateway flushes each upstream fragment as it arrives.Tool calling
Both models accept OpenAI-style tools. DeepSeek Web and guest GLM do not expose a native client-tool API, so Flashline bridges calls through a compact prompt and parses the model's call markup. Use the same tools schema you would send to OpenAI.
- Chat completions: the assistant message has
content: null, atool_callsarray, andfinish_reason: "tool_calls". Streaming sends the calls asdelta.tool_callschunks. - Responses: the output contains a
function_callitem withcall_id,name, andarguments. Streaming emitsresponse.function_call_arguments.*andresponse.output_item.*events. - Continuations: send the tool result back with the returned
previous_response_idorconversation. The gateway reuses the upstream chat, so the full transcript is not replayed. - GLM: client tools disable the guest model's built-in web search/fetch for that request. GLM calls are parsed from
<tool_call>{...}</tool_call>or bare JSON; DeepSeek's DSML markup is not used on GLM. - Best effort: malformed or unknown tool names are ignored, and invalid arguments are passed through so your tool can return an error. Tool results count as billed input tokens.
{
"model": "glm-5.3-flash",
"messages": [{ "role": "user", "content": "What is the weather in Paris?" }],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
}]
}
{
"choices": [{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"Paris\"}" }
}]
},
"finish_reason": "tool_calls"
}]
}
Conversations
DeepSeek and guest GLM keep conversation state upstream. Flashline scopes every session to the account that created it, so two users with the same prompt never share a conversation. You can continue a turn in three ways:
- Pass the returned
previous_response_id(resp_…) with your next message. - Pass the returned
conversationID explicitly. - Send the full
messageshistory; the gateway matches it to the existing session.
If an ID belongs to another account, the request returns 404 conversation_not_found.
Billing
User keys are prepaid and metered. Each request reserves a projected amount, then settles against the final token counts. The current phase (peak or off-peak) is determined at request start using the published UTC windows. Input is billed at the cache-miss rate.
accumulated_token_usage field is the chat's context-window fill, not output; Responses API calls expose it as metadata.upstream_context_tokens and it is never billed as completion tokens.| Item | Off-peak | Peak |
|---|---|---|
| Input · cache miss | $0.15 / 1M | $0.30 / 1M |
| Output | $0.60 / 1M | $1.20 / 1M |
| Input · cache hit (reference) | $0.003 / 1M | $0.006 / 1M |
| Web search | $0.00075 / request | $0.00075 / request |
Peak hours are 01:00–04:00 and 06:00–10:00 UTC, Monday–Friday, excluding Chinese public holidays. Rates can be adjusted by the operator; the live values are always shown on the pricing section and in the dashboard.
GLM-5.3-Flash guest billing
GLM has a separate price book with flat Z.ai list rates and no peak/off-peak phase. Usage rows and receipts include provider: "glm" and the actual model. Cached input tokens are billed at the cached rate when the upstream reports them.
| Item | Price |
|---|---|
| Input · cache miss | $0.15 / 1M |
| Input · cached | $0.03 / 1M |
| Output | $0.50 / 1M |
| Web search | $0.01 / request |
An empty balance returns 402 insufficient_balance before the upstream call is made. A request already in flight may settle slightly below zero; the next request is blocked until credit is added.
Limits
| Limit | User key | Operator key |
|---|---|---|
| Requests per minute | 60 | 240 |
| Concurrent requests per account | 3 | — |
| Queueing | Independent DeepSeek and GLM lanes; excess requests wait | Same |
| Request body | 24 MB | 24 MB |
Limits are enforced at the gateway and can be tuned by the operator. Exceeding a limit returns 429 with a code such as rate_limited, too_many_requests, or server_busy.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | model_not_found | The requested model is not served by this gateway. |
| 400 | invalid_json | The body is not a JSON object. |
| 401 | invalid_api_key | Missing or unknown key. |
| 402 | insufficient_balance | No prepaid credit available. |
| 403 | account_disabled | The account has been disabled. |
| 403 | opencode_client_required | Missing or incorrect OpenCode client headers. |
| 404 | conversation_not_found | The conversation ID is unknown or belongs to another account. |
| 413 | payload_too_large | The body exceeds the gateway limit. |
| 429 | rate_limited | Per-minute request limit reached. |
| 429 | too_many_requests | Per-account concurrency limit reached. |
| 429 | server_busy | The provider queue is full. Try again shortly. |
| 500 / 502 | glm_upstream | The guest GLM upstream failed or returned an empty response. |
| 502 | glm_blocked | The guest GLM completion was blocked by the upstream WAF (HTTP 405). Retry later. |
| 503 | provider_disabled | GLM is disabled on this deployment. |
Errors use the shape { "error": { "message": "…", "code": "…" } }. Streaming errors are sent as an SSE event before the stream closes.
DeepSeek Web sometimes returns an empty turn or ends a tool turn with a plan instead of a tool call. The gateway retries empty turns with a recovery prompt and continues announced-but-unexecuted tool steps automatically. Retried attempts are not streamed, so the client only sees the successful attempt. If every retry is empty, the response contains a [Gateway] fallback message that asks the client to retry.
Health & models
GET /health is public and returns uptime, user count, request count, and the served models. GET /v1/models requires a key plus the OpenCode client headers and lists deepseek-v4.1-flash plus glm-5.3-flash when GLM is enabled.
curl __ORIGIN__/health curl __BASE_URL__/models \ -H "Authorization: Bearer $FLASHLINE_KEY" \ -H "X-Title: opencode" \ -H "X-Flashline-Client: opencode" \ -H "X-Flashline-Client-Token: $FLASHLINE_CLIENT_TOKEN"
Support
For account issues, use the Billing page to submit a top-up request with a note, or contact the operator of this Flashline deployment. Flashline is an independent gateway and is not affiliated with DeepSeek.