
What you are actually calling
deepseek-v4-flash is the smaller of the two models on DeepSeek's price card: a 284B-total / 13B-active Mixture-of-Experts LLM with MIT-licensed open weights, and the successor to the generation I covered in DeepSeek V3.2.
The alias is a moving target rather than a pinned snapshot. DeepSeek's changelog note says the model "has been updated to DeepSeek-V4-Flash-0731. The calling method remains unchanged", so you always get the newest build and there is no published way to pin an older one.
Everything you need to configure lives on the one page, and it is worth a look before you write a line of code, because both base URLs and both prices sit in the same table.

Two things in that screenshot decide most of your architecture. The price card lists 1M context and 384K max output for both models, so the price gap between Flash and Pro is not a context window tradeoff. And thinking mode is listed there as supporting both modes with thinking as the default, which in terms of cost is the single most expensive default in the whole API.
Here is that same card as numbers, since you will be doing arithmetic with them shortly:
| Billing item (per 1M tokens) | deepseek-v4-flash | deepseek-v4-pro |
|---|---|---|
| Input, cache hit | $0.0028 | $0.003625 |
| Input, cache miss | $0.14 | $0.435 |
| Output | $0.28 | $0.87 |
| Concurrency limit | 2,500 | 500 |
| Responses API | ✓ | ✗ (early Aug 2026) |
Flash sits at roughly a third of Pro's cache-miss input and output rates, both quoted per million tokens. If you want the awkward part of that story, the cheap tier currently outscores the expensive one on DeepSeek's own agentic rows, which I dug into separately in Flash vs V4 Pro.
For how it lands against the rest of the field, there is Flash vs Kimi K3 and Flash vs GPT-5.6. The naming collision with Qwen 3.7 Flash is unfortunate and not my fault.
Before you start
Four prerequisites, and only one of them is an unusual one.
- An account and a key. Keys are created at platform.deepseek.com/api_keys. Read it from an environment variable and not inline, which is also how DeepSeek's own samples does it.
- The plain OpenAI SDK.
pip3 install openaiornpm install openai. There is no DeepSeek package to install anywhere, which is the whole point of the compatibility layer. - Money on the account, up front. DeepSeek is prepaid, and this is the prerequisite that bites. A
402 - Insufficient Balancedoes not arrive at setup time, when you would actually notice it. Auth succeeds, the first calls succeed, then the failure appears whenever the balance hits zero, which on a batch loop means partial completion with a payment error sitting at an arbitrary row index. - Knowing which model string you want. Every code sample in DeepSeek's docs hardcodes
deepseek-v4-pro. Copy one and expect Flash pricing, and you get billed at 3.11x on input and output.
There is three hosts, not one, and the docs spread them across different pages:
| Base URL | What it is for |
|---|---|
https://api.deepseek.com | OpenAI-compatible Chat Completions, plus the Responses API |
https://api.deepseek.com/anthropic | Anthropic message format, x-api-key auth |
https://api.deepseek.com/beta | Beta features: prefix completion and tool-calling strict mode |
There is no /v1 variant on the current docs. If you have seen that suffix in an older tutorial somewhere, it is simply not in the config table as it stands today.
Step 1: your first call
Here is the Python, with the model string swapped over to Flash and thinking left at DeepSeek's default, so that you can see what the default actually does to you:
# pip3 install openai
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ.get('DEEPSEEK_API_KEY'),
base_url="https://api.deepseek.com")
response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": "Hello"},
],
stream=False,
)
print(response.choices[0].message.content)
print(response.usage)
The same thing as curl, if you would rather see the wire format:
curl https://api.deepseek.com/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${DEEPSEEK_API_KEY}" \
-d '{
"model": "deepseek-v4-flash",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"}
],
"stream": false
}'
Print response.usage on that first call, not only the content. It is the one way to see the thing I am about to describe.
Step 2: turn thinking off, or know that you are paying for it
thinking.type accepts enabled or disabled, and the API reference gives the default as enabled. reasoning_effort accepts low, high and max, and the same page says "the default effort is high". Nobody sets those on a first call, which means your hello-world ran at high reasoning effort and you were billed for the chain of thought at the output rate.
Turning it off is one argument, and it goes inside extra_body, because the OpenAI SDK has no native thinking field of its own:
response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[{"role": "user", "content": "Summarise this ticket in one line: ..."}],
extra_body={"thinking": {"type": "disabled"}},
)
Turning it down rather than off is where Flash has a real advantage over its sibling. The published effort mapping remaps what you ask for per model:
| Requested effort | deepseek-v4-flash serves | deepseek-v4-pro serves |
|---|---|---|
low | low | high |
high | high | high |
xhigh | high | max |
max | max | max |
Read the first row twice. low is a real low on Flash and is silently upgraded to high on Pro, so there is no cheap Pro run at all right now. Flash has it's own quirk on the third row, where xhigh collapses down to high, so asking for more than high and less than max just gets you high anyway. DeepSeek footnotes that it "will update the actual mapped effort of deepseek-v4-pro in early August 2026", which is now, so re-check the Pro column if you care about it.
Two side effects of leaving thinking on that catches people out. First, per the thinking mode guide, thinking mode does not support temperature, top_p, presence_penalty or frequency_penalty, and DeepSeek is explicit that "setting these parameters will not trigger an error but will also have no effect". Second, the chain of thought comes back on a separate field, reasoning_content, so a script printing only .message.content shows you the answer and none of the tokens that you paid for.
That is enough moving parts that guessing the bill is a bad idea. Plug in your own shape:
Step 3: stream it, and keep the token counts
Streaming is one argument. Getting usage data out of a stream is a second one that people forget, and then wonder why every chunk reports usage: null:
stream = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[{"role": "user", "content": "Draft a refund reply."}],
stream=True,
stream_options={"include_usage": True},
extra_body={"thinking": {"type": "disabled"}},
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
if chunk.usage:
print("\n", chunk.usage)
With include_usage on, one extra chunk arrives before data: [DONE], carrying the whole-request token counts with an empty choices array. The API reference says to only set it when stream is true.
If you are writing your own SSE parser instead of using the SDK, there is one more thing worth to flag. While a request waits for capacity, the connection gets padded. Per the rate limit page, non-streaming requests "continuously return empty lines" and streaming requests return : keep-alive SSE comments. A hand-rolled reader that treats a blank line as end-of-body, or that does not skip the lines starting with :, breaks right here. Also, if inference has not started after ten minutes, the server just closes the connection on you.
Step 4: multi-turn, because the API remembers nothing
DeepSeek is quite direct about this one. The multi-round guide calls /chat/completions a "stateless" API, "meaning the server does not record the context of the user's requests. Therefore, the user must concatenate all previous conversation history and pass it to the chat API with each request."
So the transcript is yours to own:
messages = [{"role": "user", "content": "What's the highest mountain in the world?"}]
response = client.chat.completions.create(model="deepseek-v4-flash", messages=messages)
messages.append(response.choices[0].message) # round 1 answer
messages.append({"role": "user", "content": "What is the second?"})
response = client.chat.completions.create(model="deepseek-v4-flash", messages=messages)
Resending the entire history on every single turn sounds ruinous, and it would be, except this is exactly where the pricing gets interesting. Context caching on disk is enabled by default for all users, no code change needed, and the cache-hit input rate is $0.0028 against $0.14 on a miss. On the same tokens, that is a 50x spread.

The caching is automatic, which means there is no knob to turn, and your prompt structure is the knob. A few mechanics decides which rate you end up paying:
- A request only bills at the hit rate if it fully matches a persisted cache prefix unit. Partial overlap of a unit does not count, which DeepSeek attributes to its Sliding Window Attention mechanism.
- Units get persisted, per the caching guide, at request boundaries, on common-prefix detection across requests, and at fixed token intervals for long inputs.
- You can audit the split per call:
usagecarriesprompt_cache_hit_tokensandprompt_cache_miss_tokens. - The cache is best-effort with no guaranteed hit rate, and unused entries clear "usually within a few hours to a few days".
Practically: keep the system prompt byte-identical, append to the history rather than rewriting it, and anything that varies per request goes at the end. Injecting a timestamp or a shuffled knowledge base snippet at the top of the prompt is how teams accidentally pay 50x, which makes cache behaviour a real prompt engineering concern rather than a billing footnote.
This bites hardest on RAG, where the retrieved chunks change on every call by design. If that is what you are building, then our support RAG pipeline walkthrough and the RAG vs raw LLM comparison are the two I would read next.
Step 5: tool calls, and the 400 that will confuse you
The tools shape is standard OpenAI, capped at 128 functions with function names limited to 64 characters:
tools = [
{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Look up an order's shipping status by order ID.",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "The order ID, e.g. A-10293"}
},
"required": ["order_id"]
},
}
},
]
tool_calls comes back as an array on the assistant message, arguments arrives as a JSON string and not a dict, and the loop-termination signal in DeepSeek's agent loop sample is that tool_calls is None when the model is done. The tool-calls guide is also careful to state the obvious thing that people still get wrong: "The model itself does not execute specific functions."
Now the part that is worth tattooing somewhere. Thinking mode's normal rule is that intermediate reasoning_content "does not need to participate in the context concatenation", and gets ignored if you pass it anyway. Add tools, and that inverts:
Please note that for requests carrying the
toolsparameter, thereasoning_contentmust be fully passed back to the API in all subsequent requests. If your code does not correctly pass backreasoning_content, the API will return a 400 error.

This is the trap because it is documented on a different page from the tool-calls guide that most people land on, and because the natural thing to write is a serializer which keeps role, content and tool_calls, then drops the field it does not recognise. Append the whole message object back instead:
messages.append(response.choices[0].message) # keeps reasoning_content intact
for tool in response.choices[0].message.tool_calls:
result = TOOL_MAP[tool.function.name](**json.loads(tool.function.arguments))
messages.append({"role": "tool", "tool_call_id": tool.id, "content": result})
One more thing here: DeepSeek never uses the phrase "parallel tool calls" and there is no parallel_tool_calls parameter on the Chat Completions surface, though the official loop does iterate the array rather than taking [0]. So write the loop, do not assume the guarantee. This is the mechanism behind anything you would call an AI agent, so it is worth getting right before layering agentic behaviour on top.
If you need schema enforcement, strict mode does exist, but it lives over on the beta host. Three requirements: base_url="https://api.deepseek.com/beta", "strict": true inside each function, and additionalProperties: false on every object, with all properties marked required. minLength, maxLength, minItems and maxItems are not supported. It is worth knowing that DeepSeek's own example uses "$def" (singular) as the definitions container rather than JSON Schema's $defs, so copy their spelling.
Step 6: JSON output
response_format={'type': 'json_object'}, and there is no json_schema variant here. DeepSeek's four-point notice is short and every point is load-bearing: set the parameter, include the word "json" in a system or user prompt and provide an example of the shape you want, set max_tokens sensibly "to prevent the JSON string from being truncated midway", and know that "the API may occasionally return empty content".
That last one is an acknowledged open bug, in bold on DeepSeek's own page, and prompt changes are the only mitigation they offer. So parse defensively:
response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[
{"role": "system", "content": 'Extract intent and urgency as json. Example: {"intent": "refund", "urgency": "high"}'},
{"role": "user", "content": ticket_body},
],
response_format={'type': 'json_object'},
max_tokens=400,
extra_body={"thinking": {"type": "disabled"}},
)
raw = response.choices[0].message.content
parsed = json.loads(raw) if raw and raw.strip() else None
Notice that DeepSeek's own JSON sample does not set max_tokens at all, despite their own requirement #3 saying to. Set it.
The Responses API, and what it does not do
Flash is the only model the Responses API supports right now. DeepSeek's own callout says it "currently only supports the deepseek-v4-flash model", with Pro support due in early August 2026. It exists mostly for the one reason, which DeepSeek says outright: "To meet the demand for Codex."
response = client.responses.create(
model="deepseek-v4-flash",
instructions="You are a helpful assistant.",
input="Hi, how are you?",
)
print(response.output_text)
Here is where the expectations break. If you know the Responses API from somewhere else, you know it for statefulness. DeepSeek's implementation is stateless. previous_response_id and conversation are "Not supported (stateless API)", store is "Not supported. The response always carries store: false", and background, metadata, include, prompt and stream_options are all unsupported too.
And they do not error. DeepSeek's own line in the compatibility table, verbatim: "Unsupported parameters are silently ignored and do not cause errors, so existing Responses API clients can connect without modification". Pass store: true, get a 200, store nothing.
What it does add over Chat Completions is worth knowing: typed semantic SSE events including a dedicated response.reasoning_text.delta channel for chain of thought, a server-side web_search built-in tool, and top_logprobs. The built-in tool shelf is exactly two items though, web_search and apply_patch; file_search, code_interpreter, computer_use and MCP are all listed as ignored.
Two migration gotchas: there is no data: [DONE] sentinel, so terminate on response.completed / response.incomplete / response.failed; and input_image parts do not error, they are "replaced with a placeholder text", which is consistent with Flash being text-only rather than multimodal.
Handling the errors you will actually hit
Seven codes are documented, and no others. There is no Retry-After header, and no backoff schedule published anywhere either, so your wrapper is the one deciding.
| Code | What it means | What to do |
|---|---|---|
| 400 - Invalid Format | Malformed request body | Fix the code. Also the code you get for missing reasoning_content with tools |
| 401 - Authentication Fails | Wrong API key | Fix the key |
| 402 - Insufficient Balance | Prepaid balance is empty | Alert a human and stop. Do not retry |
| 422 - Invalid Parameters | Well-formed body, invalid values | Fix the code |
| 429 - Rate Limit Reached | Concurrency ceiling hit | Back off and retry |
| 500 - Server Error | DeepSeek-side issue | Retry after a brief wait |
| 503 - Server Overloaded | High traffic | Retry after a brief wait |
The 400-versus-422 split is real and useful. 400 is a malformed body; 422 is a well-formed body carrying bad parameter values. Two different debugging paths, and the error message is what tells you which one.
The 429 deserves a note, because DeepSeek's own documented fix is unusually candid: "Please pace your requests reasonably. We also advise users to temporarily switch to the APIs of alternative LLM service providers, like OpenAI." A vendor recommending a competitor inside it's own error docs is a real signal about peak-load capacity, and it is worth designing a fallback path for instead of treating as a joke.
On limits themselves: DeepSeek publishes no RPM or TPM figure at all. The only ceiling is concurrency, and the rate limit page counts it per account rather than per key, so minting extra keys buys nothing. "A request counts as one concurrent connection from the time it is sent until the model response is complete", which means a long reasoning call holds a slot for its full duration. Flash gets 2,500 slots against Pro's 500, and expansion is free but goes through a manual Feishu form.
If you are running multi-tenant traffic, set user_id (note: not OpenAI's user), passed as extra_body={"user_id": "..."}. It drives content-safety review, per-user scheduling isolation, and KV cache isolation for privacy. Format is [a-zA-Z0-9\-_], max 512 characters, and DeepSeek warns not to put user privacy information in it. It is also your only lever against prompt injection blast radius across tenants, which matters more than it sounds once real users are typing into the thing.
The five things that fail silently
Every trap in this API returns HTTP 200. That is the through-line here, and it is worth one list that you can check a diff against.

frequency_penaltyandpresence_penaltyare deprecated. Both carry the same line in the API reference: "This parameter is no longer supported. It will not take effect if you pass it to the API." Strip them when porting an OpenAI call.temperatureandtop_pare inert in thinking mode, which is the default. If you tuned a prompt attemperature=0.2and ported it over, you are running at whatever thinking mode does.- Unsupported Responses API fields are silently ignored.
store,previous_response_id,conversation,background. 200 every time. - An unrecognised model name on the Anthropic endpoint becomes Flash. Per the Anthropic API guide, any unsupported model name "will automatically map it to the
deepseek-v4-flashmodel".claude-opus*maps to Pro,claude-sonnet*andclaude-haiku*map to Flash. DeepSeek pitches this as a feature for pointing Claude clients at their API, and it is, right up until a typo silently changes which model your evals ran against. cache_controlis ignored on the Anthropic endpoint. Everywhere it appears: on tools, text blocks,tool_use,tool_result. DeepSeek's own disk cache runs instead, so there is nothing to declare, but code ported from Anthropic loses its explicit cache breakpoints without a word.
I would add on a sixth that is not really DeepSeek's fault. finish_reason carries a non-OpenAI value, insufficient_system_resource, returned when the request is interrupted by inference-system capacity. A handler that only knows stop / length / tool_calls is going to treat a truncated answer as a complete one.
Should this answer customer tickets?
This is the question I actually get asked, and the honest answer is that the API is the easy part of it. Getting a model to reply is a weekend. Getting an AI support agent you would let anywhere near a real queue is not.
Here is the thing I have watched go wrong inside our own product, which is more instructive than any benchmark. The worst failure mode eesel has observed in production is not a model refusing, or timing out. It is an agent fabricating success: narrating "executing Zendesk searches" for roughly ten turns without ever hitting the API, reporting files saved that do not exist, inventing metrics. We only caught it because we were looking for it. Nothing kills a teammate faster than lying about what it did, and note the shape of that failure, it looked like a 200 as well.
Which is the same lesson as this whole post. A raw model call succeeding tells you almost nothing about whether the answer was actually right. Independent testing puts Flash's hallucination rate at 84%, down 12 points from its predecessor but still nowhere near "point it at customers and walk away". It's score also swings from 29 non-reasoning up to 50 at max effort, depending on a setting that you may not have set on purpose.
Which means the layers sitting above the model are the ones doing the real work: grounding in verified sources, a confidence score so it declines rather than guesses, clean escalation paths, and a human reviewing anything consequential.
If you want the long versions of all that, we have written up preventing hallucinations and also adversarial testing separately.
Then there is the data question, and I want to be precise here rather than alarming about it. DeepSeek's Open Platform Terms of Service governs the paid API and is silent on training use of your inputs, which is different from permissive and also different from safe. The consumer Terms of Use carries an explicit clause at §4.3 with an opt-out toggle; the API-specific document simply stops before that clause. There is no published DPA and no zero-retention option either way, and the data itself sits under PRC law.
If you lived through Slack's policy clarification, then you know how that reads to a security reviewer. It is worth pairing with our guide to SOC 2 and GDPR before customer data goes near it, and thinking about what data you would be sending in the first place.
If none of that is acceptable, the MIT weights are a real out. You can self-host, fine-tuning included, and the licence permits commercial use.
Whether you should is the build versus buy question, and my honest read, from having shipped both, is that a support team's constraint is almost never the model access. It is integration depth and escalation quality that decide whether any of it works.
Try eesel
If you got here because you are wiring Flash into a helpdesk, then the API is about 5% of that project. The other 95% is what happens when the model is wrong, and that is the part I would rather you not build twice.
eesel is that 95%, productised. It grounds every reply in your verified knowledge, so help center articles, past tickets, macros and connected docs, then it does the thing that matters most before go-live: simulations against your own historical tickets, so you see the real accuracy on your real queue instead of a benchmark number. You deploy when it clears your own bar, not when some leaderboard says so. Pricing is 40¢ per ticket handled, no seat fees, and you are never charged for tickets your humans handle, so if you route 200 of your 1,000 monthly tickets to AI you pay for 200. $50 of free usage, no credit card, and every integration is available on the free tier.

That "approval / rejection usage per tool" panel is the direct answer to the fabricated-success problem from above. Every tool action the agent takes is countable and reviewable, so an agent claiming it searched your helpdesk becomes a row that you can check, rather than a sentence you have to trust.
Put differently: a raw API is infrastructure. What a support manager actually needs is an employee.
If you want to check the token math against the per-outcome math first, start with AI customer service cost, and then cost per resolution for the unit which actually shows up on a budget.
There is a version of support automation that reduces costs without wrecking your CSAT. It starts from cost per ticket, and not from cost per million tokens.
Frequently Asked Questions
How do I use the DeepSeek V4 Flash API for the first time?
base_url at https://api.deepseek.com, set your key from DEEPSEEK_API_KEY, and pass model="deepseek-v4-flash". There is no DeepSeek-specific package. The one thing to add on your very first call is extra_body={"thinking": {"type": "disabled"}}, because thinking mode is on by default and its reasoning output bills at the output rate.How much does the DeepSeek V4 Flash API cost?
Does the DeepSeek V4 Flash API support tool calling and JSON output?
tools for function calls and response_format={'type': 'json_object'} for JSON. The trap is that with tools present you must resend reasoning_content in later turns or the API returns a 400. If you are wiring this into a helpdesk, our support chatbot build guide covers the layer above the model.What is the rate limit on the DeepSeek V4 Flash API?
Is the DeepSeek API safe for customer data?
Can I run DeepSeek V4 Flash myself instead of using the API?
Should DeepSeek V4 Flash answer customer tickets directly?

Article by
Rama Adi Nugraha
Rama is a software engineer at eesel AI with two years of experience writing about B2B SaaS, AI tools, and customer support technology. Based in Bali, Indonesia, he brings a developer's perspective to product comparisons — cutting through marketing copy to what the integrations and APIs actually do.








