On 13 November 2026 DeepSeek opened up the tools behind its own chat app to developers. Its V4.1-Flash model, called deepseek-flash in the API, can now search the web, run multi-step “deep thinking” searches, read links that users paste and screen content for safety. DeepSeek runs all of this on its own servers, so you don’t need to build a search or scraping layer. This tutorial explains how the new web tools work, shows working Python code for search and URL reading, and covers the details that can trip you up: multi-turn history, errors and billing.

What DeepSeek released

According to the official announcement, deepseek-flash now supports these built-in capabilities in all three API styles DeepSeek offers: the Anthropic-style Messages API, the OpenAI-style Responses API and the classic Chat Completions API.

  • Web Search: the model searches the web (and images) and answers with citations, with thinking disabled.
  • Deep Thinking Search: with thinking enabled, the model searches, opens pages and finds text in pages over several reasoning steps.
  • File Input: PDFs, Office files and images are parsed into the conversation context.
  • URL Reading: links in the user’s message are fetched and used to answer.
  • Safety Checks: inputs and outputs are screened, and the response signals a refusal when a risk is detected.

Search, Fetch, OCR, speech-to-text (ASR) and text-to-speech (TTS) are also offered as standalone APIs. DeepSeek published an open-source demo client at deepseek-ai/chat-demo so you can see the whole flow.

One rule matters straight away: public-facing apps must enable safety checks. DeepSeek also says developers remain responsible for content moderation, any required model registration and labelling AI-generated content.

How the web tools work

The Web Tools guide describes two “server tools”. You declare them in the tools array, but your code never executes them. The model decides whether to go online, and DeepSeek runs the search or fetch.

Tool Thinking Behaviour
deepseek_web_search disabled Web search with cited answers
deepseek_web_search enabled Deep thinking search (search, open page, find in page)
deepseek_read_url either Reads links in the last user message

A few rules from the docs are worth memorising:

  • You cannot force these tools with tool_choice. Naming them there returns a 400. Setting tool_choice to none turns all tools off, including URL reading.
  • They cannot yet be combined with other server tools. Mixing them returns a 400 error.
  • allowed_domains, blocked_domains and user_location are not supported.
  • safety_check takes a level of low or high.

Setup

Create an API key on the DeepSeek platform and export it:

export DEEPSEEK_API_KEY="sk-..."
pip install requests

We’ll call the HTTP API directly with requests. That way the DeepSeek-specific fields (safety_check, server_tool_calls, annotations) are plain JSON you can see, rather than extras hidden inside an SDK.

Example 1: web search with citations

This script uses the Chat Completions endpoint. It declares both tools, turns on safety checks and prints the answer and its sources.

import os
import requests

API = "https://api.deepseek.com/chat/completions"
HEADERS = {
    "Authorization": f"Bearer {os.environ['DEEPSEEK_API_KEY']}",
    "Content-Type": "application/json",
}

def ask(messages):
    payload = {
        "model": "deepseek-flash",
        "tools": [
            {"type": "deepseek_web_search"},
            {"type": "deepseek_read_url"},
        ],
        "safety_check": {"level": "high"},
        "messages": messages,
    }
    r = requests.post(API, headers=HEADERS, json=payload, timeout=180)
    r.raise_for_status()
    body = r.json()
    # A generation failure can still come back as HTTP 200
    if "error" in body:
        raise RuntimeError(body["error"])
    return body

body = ask([{"role": "user", "content": "What changed in llama.cpp v1.0.0?"}])
msg = body["choices"][0]["message"]
print(msg["content"])

for step in msg.get("server_tool_calls", []):
    for call in step["calls"]:
        print(call["kind"], call["action"], call["status"])

for ann in msg.get("annotations", []):
    cite = ann["url_citation"]
    print("source:", cite["url"])

What you get back:

  • content is the answer text. It has no inline citation markers.
  • annotations holds url_citation items with start_index and end_index (counted in Unicode code points) so you can link spans of the answer to sources.
  • server_tool_calls is a DeepSeek extension. Each entry is one step with its calls. Each call has a kind (search, deep_search, read_link, image_search or deep_image_search), an action, a status and either sources, page or error.

Also check choices[0].finish_reason. A value of content_filter means the safety review refused the reply. In that case content is empty, so show a friendly message instead.

Example 2: answer from a link the user pasted

To read links without searching, declare only deepseek_read_url. Only the last user message is checked for URLs, repeats count once and at most the first 10 different URLs are considered.

payload_tools = [{"type": "deepseek_read_url"}]

def read_links(question):
    r = requests.post(API, headers=HEADERS, json={
        "model": "deepseek-flash",
        "tools": payload_tools,
        "safety_check": {"level": "high"},
        "messages": [{"role": "user", "content": question}],
    }, timeout=180)
    r.raise_for_status()
    return r.json()

body = read_links(
    "Summarise https://api-docs.deepseek.com/news/news261113/ in three bullet points."
)
print(body["choices"][0]["message"]["content"])

The page text never comes back to you in plain form. For a link that was read, the call carries an encrypted signature, and DeepSeek restores the page from that signature on later turns.

Multi-turn chats: pass replies back unchanged

This is where most integrations break. The docs say you must send the full history every time and pass each previous assistant reply back unchanged, including server_tool_calls, kind and signature. If a framework or gateway strips server_tool_calls, the request still works, but the pages read earlier drop out of the context.

history = [{"role": "user", "content": "Summarise https://example.com/ in one sentence."}]
first = ask(history)["choices"][0]["message"]

# Keep the assistant message exactly as returned
history.append({
    "role": "assistant",
    "content": first["content"],
    "server_tool_calls": first.get("server_tool_calls", []),
})
history.append({"role": "user", "content": "Quote the first sentence of that page."})

second = ask(history)["choices"][0]["message"]
print(second["content"])

Also note that search results, opened pages and thinking from earlier rounds are not added to later rounds. Only the answer text carries forward. A page read from a link is the exception, because it is restored from its signature.

Deep thinking search

Enable thinking and keep deepseek_web_search declared, and the model switches to deep thinking search. The docs say it makes up to about 20 tool calls before answering, using search, open_page and find_in_page steps. That gives better answers to research questions but takes longer and uses more tool calls. In Chat Completions the request includes "thinking": {"type": "enabled"}. In the Responses API the docs use "reasoning": {"effort": "high"}.

With thinking enabled and search declared, links in the message are handled by the deep search rather than read separately.

Errors and billing to plan for

  • HTTP 200 does not always mean success. If generation fails after it starts, Chat Completions returns {"error": ..., "usage": ...} with status 200. Check for error in every response, as the example above does.
  • Replies that used web tools cannot be continued with prefix completion. Resend the previous request instead.
  • Tool calls are billed on top of tokens. A search call is billed per query, failed or empty searches are not billed, and finding text inside a page is free. Read usage.server_tool_use.tool_search, tool_fetch and tool_image_search for billed counts, and check the current rates on DeepSeek’s Models & Pricing page.
  • Blocked input returns 400 Content Exists Risk.

When to use it

The built-in tools suit research assistants, support bots that must answer from a pasted help-centre link, and document Q&A where you would otherwise run your own crawler. If you need strict domain allow-lists or want to combine with other server tools, those aren’t supported yet. In that case call the standalone Search and Fetch APIs from your own code.

Key takeaways

  • deepseek-flash now offers server-side web search, deep thinking search, URL reading, file input and safety checks.
  • Declare deepseek_web_search and/or deepseek_read_url in tools. Never name them in tool_choice.
  • Turn on safety_check for any public app. DeepSeek requires it.
  • Pass assistant replies back unchanged, including server_tool_calls and signature, or earlier pages are lost.
  • Check every response for an error field, even when the status is 200.

Source: DeepSeek API Docs: Web Tools and The full DeepSeek App experience, now via API.