Why this lesson exists
DeepSeek deliberately made itself a drop-in replacement. That is excellent news and also the reason people skip learning the API — then spend an afternoon confused about a response field. This lesson is thirty minutes of setup followed by an hour of never being confused again.
Step one: a key, stored properly
Create your API key in the DeepSeek platform console. Then put it somewhere that is not your source code, not your shell history, and not a chat window.
Three storage rules that matter more than they sound:
- Environment variable or a secrets file with restrictive permissions. Read it at runtime. Never inline it in a script you might commit.
- One key per service. If a key is only used by one thing, revoking it costs you nothing and tells you exactly what broke.
- Never echo it. Not into a log, not into a screenshot, not into a chat with an assistant. The moment a secret appears in a transcript, its secrecy is gone — you cannot un-leak it by deleting the message.
# Store it once, with tight permissions
umask 077
echo 'DEEPS...EY=your-key-here' > ~/.deepseek.env
chmod 600 ~/.deepseek.env
# Load it in a shell session
set -a && source ~/.deepseek.env && set +a
Step two: the two base URLs
This is the whole "SDK compatibility" story, and it is two lines long.
| Format | Base URL | Use when |
|---|---|---|
| OpenAI-compatible | https://api.deepseek.com | Default. Any OpenAI SDK or tool works unchanged. |
| Anthropic-compatible | https://api.deepseek.com/anthropic | You already have Claude-format code, or a tool speaks the Anthropic API (Claude Code, some harnesses). |
That is why no SDK needs rewriting. You are not adapting your code to DeepSeek — you are pointing an existing client at a different host. And note the practical consequence: it means most agent tools and coding assistants can use DeepSeek "for free," by config, without a plugin. That fact is the entire premise of Module 7.
Step three: the first request
curl https://api.deepseek.com/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $DEEPS...EY" \
-d '{
"model": "deepseek-flash",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain prefix caching in two sentences."}
],
"thinking": {"type": "enabled"},
"reasoning_effort": "high",
"stream": false
}'
Every field here is worth knowing on sight.
| Field | What it does | Default |
|---|---|---|
model | Which model serves the request | Required. Send deepseek-flash. |
messages | The full conversation array | Required. See the note below — this is not stateful. |
thinking | {"type":"enabled"} or disabled | Enabled by default. |
reasoning_effort | low / high / max | Model default. Lesson 9 covers choosing. |
stream | Server-sent events instead of one response | false. Use streaming for anything a human watches. |
The API is stateless — this is the number one first-week confusion
The web chat remembers your conversation. The API does not. Every call must resend the entire message array. If you send one user message on turn two, the model has no idea what turn one was.
This sounds obvious written down and it is genuinely surprising the first time it bites. It is also the reason context engineering (Lesson 11) is a real discipline: you own the history, so you own the cost of it.
Reading the response
The shape is the same as any OpenAI-compatible response, with one addition that matters:
{
"id": "...",
"model": "deepseek-flash",
"choices": [{
"message": {
"role": "assistant",
"content": "Prefix caching stores ...",
"reasoning_content": "The user wants two sentences. Key points: ..."
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 42,
"completion_tokens": 118,
"prompt_cache_hit_tokens": 0,
"prompt_cache_miss_tokens": 42
}
}
Three things to notice:
reasoning_contentcarries the thinking trace, separate from the answer. Read it whenever output surprises you — it is the best debugging tool you have, and it is free to read.modelechoes what actually served the request. This is your alias detector from Lesson 2.- The cache split in
usageis the single most important field for your bill. Hit tokens and miss tokens are priced an order of magnitude apart. Lesson 6 is built on this.
Checkpoint
- What is the base URL for the Anthropic-format endpoint, and why would you use it?
- Why does an existing OpenAI SDK work without modification?
- What must you do differently on turn two of a conversation compared to the web chat?
- Which two
usagefields predict your actual cost?
Answers
https://api.deepseek.com/anthropic— for existing Claude-format code or tools that speak that API.- DeepSeek implements an OpenAI-compatible API surface, so you only change the base URL.
- Resend the full message array. The API is stateless and holds no conversation history.
prompt_cache_hit_tokensandprompt_cache_miss_tokens.
Exercise: three calls, three lessons
- Make a basic call. Send one message, non-streaming, and print the full response object. Confirm the
modelfield saysdeepseek-flash. - Read the thinking. Send a question that requires reasoning — something with a wrong-looking premise, or a small arithmetic puzzle. Print
reasoning_contentseparately fromcontent. Find the moment the model changes its mind, if there is one. - Break it deliberately. Send a second call on the same topic with only the new user message and no history. Observe that it has amnesia. Then resend the full array. You have now felt the statelessness rather than just read about it.
Then make the same first call through the Anthropic-format endpoint with an Anthropic-style client. Both should produce equivalent output — that is the point.
Next
You can make calls. Lesson 6 explains why they cost what they cost — and how to make them cost almost nothing.