DeepSeek API: The Complete Quick-Start Guide

It starts with an error message, and this summer I watched it happen twice. A colleague typed pip install deepseek-api exactly as a tutorial instructed; pip answered with a wall of red. The other case installed fine, the sample ran, and the server replied Model Not Exist — because the tutorial's deepseek-chat string had retired on July 24, 2026, along with deepseek-reasoner. Neither failure was the person at the keyboard. The tutorials expired; the DeepSeek API moved on.

This guide is aligned to the current, August 2026 reality: the model names that actually resolve, the key flow that actually exists, and the parameters that shipped with GA. One page, zero to a production-ready call chain — key, first call, streaming, thinking mode, multi-turn, structured output, rate limits. Along the way, a correction table for the things widely-circulated tutorials still teach wrong.

What Is the DeepSeek API?

The DeepSeek API is a chat-completions interface that speaks two industry-standard dialects: the OpenAI format and the Anthropic format. That dual compatibility is the whole design philosophy. You don't adopt a new SDK, learn a new payload shape, or rewrite anything — you point your existing OpenAI client at a different base URL and change one model string. If you can call GPT, you can already call DeepSeek.

Three official domains, three different jobs, and telling them apart matters more than it should — a search for this topic surfaces at least eight lookalike sites (deep-seek.com, deepseekv4pro.com, and friends) that are neither the docs nor the platform:

DomainJob
platform.deepseek.comConsole: create keys, check balance, usage
api-docs.deepseek.comDocumentation and guides
api.deepseek.comThe endpoint your code actually calls

The current model names, and what they resolve to under the hood:

Model stringActual checkpointCharacter
deepseek-v4-flashV4-Flash-0731Fast and cheap, the daily driver
deepseek-v4-proV4-Pro-0813Deep-reasoning flagship
deepseek-v4-flash-vision-expExperimentalText plus image input

The mapping is invisible by design: the string stays stable, and DeepSeek points it at the latest checkpoint behind the scenes. What does not resolve anymore: deepseek-chat and deepseek-reasoner, both fully retired July 24, 2026. Any tutorial still typing those two names is describing a product that no longer exists. (Model specs, benchmarks, and full pricing live in our DeepSeek V4 guide.)

Get Your DeepSeek API Key

Authentication is a plain HTTP Bearer token — one string, one header. To get it: sign in at platform.deepseek.com, open the API Keys page, click create, copy the key immediately. It's shown once. That's the entire ceremony.

Two things worth saying out loud, because the ecosystem is noisy on both.

First: there is no OAuth dance, no enterprise verification form, no scope selection. Some widely-circulated tutorials describe a two-stage OAuth 2.0 flow with identity verification — that flow doesn't exist. If a guide asks you to configure OAuth credentials, you're reading fiction.

Second: treat the key like a password, because it is one — it spends money. The standard discipline, borrowed from every production codebase:

# .env — never hardcode, never commit
DEEPSEEK_API_KEY=sk-your-key-here
import os
from dotenv import load_dotenv
load_dotenv()
api_key = os.getenv("DEEPSEEK_API_KEY")
if not api_key:
    raise RuntimeError("Missing DEEPSEEK_API_KEY")

Keep it server-side. A key shipped to a browser or bundled into a mobile app isn't a secret anymore; it's a donation.

Your First DeepSeek API Call

The official first-call example runs on the raw HTTP layer. Worth reading slowly once, because every field shows up later in SDK form — and this snippet is still the first thing I run against any new account: it proves the key, the network, and the model name in one shot, before anything else in the stack can be what's wrong:

curl https://api.deepseek.com/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${DEEPSEEK_API_KEY}" \
  -d '{
        "model": "deepseek-v4-pro",
        "messages": [
          {"role": "system", "content": "You are a helpful assistant."},
          {"role": "user", "content": "Hello!"}
        ],
        "thinking": {"type": "enabled"},
        "reasoning_effort": "high",
        "stream": false
      }'

The endpoint is POST /chat/completions on api.deepseek.com. Messages stack in roles — a system persona, then the user turns. thinking switches the model's chain-of-thought stage on, reasoning_effort sets how hard it thinks, and stream: false means you wait for the whole answer in one JSON body.

The Python version

Here's what "OpenAI-compatible" buys you in practice — install the standard OpenAI SDK, not any third-party "deepseek" package (several of those are unofficial, one famous one is simply fabricated):

from openai import OpenAI

client = OpenAI(
    api_key=os.environ["DEEPSEEK_API_KEY"],
    base_url="https://api.deepseek.com",   # the adapter plug
)

response = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Explain HTTP cookies in two sentences."},
    ],
)
print(response.choices[0].message.content)

Two lines do the migration: the base_url, and the model string. Everything else — retries, types, streaming helpers — is your existing OpenAI code wearing a different badge. For codebases built on the Anthropic SDK, the same trick works against https://api.deepseek.com/anthropic.

Streaming

Set stream=True and the response arrives as server-sent events: chunks of JSON, each carrying a delta with a few new tokens. First words appear before the model finishes thinking through the whole answer, which is the difference between a chat that feels alive and one that feels like a file download.

stream = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=[{"role": "user", "content": "Write a haiku about latency."}],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)

Whether streaming or not, the response's usage field reports prompt_tokens and completion_tokens — the numbers your bill is built from. Get in the habit of logging them now; the tokens section below shows what they mean.

Two streaming habits that pay for themselves the first week — both of which I adopted expensively. Set explicit timeouts on every request — a stalled connection holding a slot open is indistinguishable from a very slow model, and you'd rather reconnect than wait forever. And when a stream dies mid-answer, retry the whole request: partial output is display material, not something the server remembers, so there's no resume token to send. Cheap requests make both habits painless, which is one quiet argument for building on the flash tier first and escalating to pro only where you can measure the difference.

Thinking Mode and Effort: The Dials That Matter

One departure from the old API: thinking mode is on by default, and the default effort is high. Before answering, the model works through a chain of thought, then answers — accuracy climbs on hard problems, latency follows.

The effort parameter is a throttle with three real positions. low is city driving — quick answers, minimal deliberation. high is highway cruising, the default sweet spot. max is the racetrack: full-budget reasoning for proofs, architecture decisions, gnarly debugging. And here's a quirk worth knowing — request medium or xhigh and the platform maps them to high. The mapping, straight from the thinking mode guide:

You requestModel actually runs
lowlow
mediumhigh
highhigh
xhighhigh
maxmax

To disable thinking for simple calls, pass the switch — and note the Python detail: the OpenAI SDK doesn't know this field, so it rides inside extra_body:

response = client.chat.completions.create(
    model="deepseek-v4-pro",
    messages=[{"role": "user", "content": "9.11 vs 9.8, which is bigger?"}],
    reasoning_effort="high",
    extra_body={"thinking": {"type": "enabled"}},
)

Two deep-water details the docs mention and most tutorials skip.

First, inside thinking mode the sampling controls — temperature, top_p, presence_penalty, frequency_penalty — do nothing. The API accepts them silently, for compatibility with existing software, and ignores them. I once lost a quiet afternoon tuning temperature before finding that line in the docs. If you're seeing zero change: that's why, not a bug in your code.

Second, the chain of thought comes back in a reasoning_content field, sitting beside content. On follow-up turns, history reasoning_content gets ignored by the server whenever the request carries no tools parameter — so you can strip old reasoning from your stored context and lose nothing.

Multi-turn Conversations: The Stateless Waiter

The chat endpoint has no memory. None. Every request is a brand-new customer: the server reads exactly the messages array you send, answers, and forgets the table entirely. A multi-turn conversation is you, the client, keeping the transcript and re-reading the whole thing to the waiter each round.

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)   # the waiter's reply

messages.append({"role": "user", "content": "What is the second?"})
response = client.chat.completions.create(
    model="deepseek-v4-flash", messages=messages
)
print(response.choices[0].message.content)

Round two ships three entries: the original question, the stored answer, the new question. This pattern — append the reply, append the next turn, resend everything — is the entire secret of conversational memory, and it's also why long agent sessions grow linearly more expensive per turn. (Prompting strategies that keep those transcripts effective are covered in the prompt guide.)

Structured JSON Output

When the output feeds a parser rather than a human, free-form prose becomes a bug. The JSON mode locks the shape, and it works on three rules: set the response format, say the word "json" in the prompt with an example, and cap max_tokens so a long answer can't truncate mid-object.

system_prompt = """
The user will provide some exam text. Please parse the "question"
and "answer" and output them in JSON format.
EXAMPLE INPUT:
Which is the highest mountain in the world? Mount Everest.
EXAMPLE JSON OUTPUT:
{"question": "...", "answer": "..."}
"""

response = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=[
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": "Which is the longest river in the world? The Nile River."},
    ],
    response_format={"type": "json_object"},
    max_tokens=512,
)
print(response.choices[0].message.content)
# {"question": "Which is the longest river in the world?", "answer": "The Nile River."}

Miss the example in the prompt and quality drops; skip max_tokens and a verbose answer can end mid-curly-brace, which is a json.loads crash you'll only meet in production.

Rate Limits and the user_id Lifeline

Concurrency is counted at the account level, regardless of which key made the call. Picture a restaurant: your account holds a fixed number of tables, and every in-flight request occupies one from send to final byte.

ModelConcurrent requests per account
deepseek-v4-pro500
deepseek-v4-flash2,500
deepseek-v4-flash-vision-exp2,500

Cross the line and the response is HTTP 429 — back off and retry, don't hammer. Need more tables? Capacity expansion requests are free; the platform matches limits to real business volume. The current numbers live on the rate limit page.

If you serve many end users under one account, user_id turns the dining room into private booths. Pass it per request — letters, digits, hyphens, underscores, up to 512 characters, nothing privacy-sensitive inside — and the platform isolates three things per identity: content-safety handling, KV cache (one user's cached context never bleeds into another's), and scheduling. In the OpenAI SDK it rides in extra_body:

response = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=[{"role": "user", "content": "Hello!"}],
    extra_body={"user_id": "customer-8801"},
)

For accounts with raised quotas, each user_id gets its own concurrency ceiling — one runaway tenant can't starve the rest.

Tokens and Your Bill

Billing runs on tokens, and the mental arithmetic is kind: one English character costs about 0.3 tokens, one Chinese character about 0.6. A 1,000-word English essay is roughly 1,300 input tokens before the model answers a single thing. The exact count always comes back in the response's usage field — that's the number to reconcile against, not any estimator, and the docs ship a tokenizer demo for offline math. Image inputs follow a size-based formula with a per-image cap.

What each token costs, per model, peak versus off-peak — that's a pricing question with its own answer sheet, and the V4 guide carries the current table with effective dates.

Beyond the Chat API

/chat/completions is the front door, not the whole house. The reference documents around it:

  • GET /models — list what your key can reach, with owner and availability
  • GET /user/balance — the wallet check, for dashboards and spend alarms
  • Files API — upload once, reference by ID (free)
  • Context caching — automatic prefix caching; hits bill at a thirtieth of a miss
  • FIM completion — fill-in-the-middle, for code-editing workflows
  • Responses API — the newer stateful-flavored format, GA since August 13
  • The Anthropic-format surface — same models, …/anthropic base path

And one door that needs no code at all: coding agents. Claude Code, GitHub Copilot, and OpenCode all take DeepSeek as a backend model through configuration alone. The Claude Code walkthrough covers the environment-variable recipe end to end — if your goal is "DeepSeek in my terminal," that's the shortcut past everything above.

Common Pitfalls in 2026

Everything above against everything still circulating, in one table:

The old tutorial saysThe 2026 reality
pip install deepseek-api, from deepseek_api import DeepSeekClientNo such official package. pip install openai, point base_url at https://api.deepseek.com
model="deepseek-chat" / "deepseek-reasoner"Retired July 24, 2026. Use deepseek-v4-flash or deepseek-v4-pro
OAuth 2.0, identity verification, enterprise license for accessBearer key from platform.deepseek.com. That's all
JWT signatures, HMAC request signingNone exist. Authorization: Bearer header
Tuning temperature in thinking modeSilently ignored while thinking is on
deep-seek.com / deepseekv4pro.com as "the official site"Official: platform.deepseek.com, api-docs.deepseek.com, api.deepseek.com

The meta-rule is older than any of it: check a tutorial's publish date before its code blocks. This API retired names in July and re-priced in August; anything written before both is archaeology.

FAQ

How do I start using the DeepSeek API?Three steps: create a key at platform.deepseek.com (API Keys page), install the OpenAI SDK (pip install openai), and construct the client with base_url="https://api.deepseek.com" plus your key. The first call is then a standard chat completion with model="deepseek-v4-flash".

Which SDK does the DeepSeek API use?No dedicated SDK exists. It's compatible with both the OpenAI and Anthropic SDKs — you reuse either one and change only the base URL (https://api.deepseek.com or https://api.deepseek.com/anthropic) and the model name.

What is the DeepSeek API base URL?https://api.deepseek.com for the OpenAI format; https://api.deepseek.com/anthropic for the Anthropic format. Chat requests go to POST /chat/completions.

Does deepseek-chat still work?No. deepseek-chat and deepseek-reasoner were retired on July 24, 2026. The replacements are deepseek-v4-flash and deepseek-v4-pro, which auto-resolve to the latest checkpoints (V4-Flash-0731 and V4-Pro-0813).

Is the API free? How do I check my balance?Pay-as-you-go, with automatic context caching softening repeat-input costs. Your live balance is one call away — GET /user/balance — and the usage field on every response gives per-call token counts to reconcile against.

What are the rate limits? What does a 429 mean?500 concurrent requests for deepseek-v4-pro, 2,500 for deepseek-v4-flash and the vision model, counted account-wide. A 429 means you crossed the ceiling: back off and retry. Higher ceilings are available through a free capacity-expansion request.

Can I call it with Anthropic-format code?Yes — base_url="https://api.deepseek.com/anthropic" with the Anthropic SDK, same model strings. Tool definitions and message shapes carry over.

Three moves from here: grab a key, run the curl snippet unchanged until a reply appears, then decide where this model belongs in your stack — a script tonight, or a coding agent with our Claude Code setup doing the heavy lifting. The real promise of a compatible interface isn't any single model's benchmark score. It's that your integration code stays yours — and the model behind it becomes a one-line decision.