DeepSeek Scraper API: Capture Answers, Reasoning, and Sources as JSON
Expert in Web Scraping Technologies
TL;DR:
- The
scraper.deepseekactor submits a prompt to DeepSeek and returns the answer as JSON. Two required inputs —promptandcountry— go in; atask_resultobject with 21 fields comes back, including the renderedmarkdown, the rawhtml, and a token count. - The reasoning trace is in the payload, not hidden behind it. Send
thinking: trueand the response gains aTHINKfragment carrying DeepSeek's step-by-step planning text plus the seconds it spent on it. - Two boolean flags change the shape of the response, not only its content.
thinkingandsearcheach add fragment types; sending both switches DeepSeek into an agentic sequence that searches, opens individual pages, and re-plans between steps. - Unknown input keys are accepted and dropped without comment.
web_search,thinking_enabled,search_enabled, andmodelall return HTTP 201 and change nothing — while a correctly-named key with the wrong type returns 400. Port a capture script from another actor and its flags disappear on the way in. - Citations arrive in three different encodings depending on the flags you sent. Plain search mode uses
[citation:N]markers against acite_index; agentic mode uses[reference:N]markers against a zero-based back-pointer array. This guide shows how to resolve both. - Free to start. New Scrapeless accounts include free trial credits — sign up at app.scrapeless.com.
Introduction: the answer is only half the payload
Search for a DeepSeek scraper API and nearly everything you find is about pointing the DeepSeek model at HTML you already fetched. That is a real technique, and Scrapeless covers it in the DeepSeek web scraping guide. This guide runs the other direction: DeepSeek is the source, and the data you want is what DeepSeek says when someone asks it a question.
That data matters for the same reason ChatGPT and Gemini answers matter. When a buyer asks an assistant which tool to pick, the answer and the pages behind it are a market signal. DeepSeek adds two things the other assistants do not hand over as readily: an exposed reasoning trace, and — when you turn on both of its capability flags — a visible record of which pages it chose to open and read in full.
The scraper.deepseek actor turns all of that into two HTTP calls: one to submit the prompt, one to collect the result. This guide covers the request shape, the complete response schema captured from live runs, a runnable Python client, and the citation-resolution logic that the payload requires and does not document.
What You Can Do With It
- Track how DeepSeek describes your category. Run a fixed prompt set on a schedule and store the
markdownanswer plus the sources behind it. - Capture the reasoning behind the conclusion. The
THINKfragment shows how DeepSeek framed the question before answering — useful when you care why a product got recommended. - Measure share of citation. In search mode the payload carries every source DeepSeek retrieved, with title, URL, site name, and publication timestamp.
- Separate the pages DeepSeek opened from the ones it merely listed. In agentic mode,
TOOL_OPENfragments record the specific URLs it read end to end — a much narrower set than the search results. - Compare markets.
countrypins the run's egress, so the same prompt can be captured for several regions and compared. - Build answer datasets. Prompt, answer, reasoning, and sources arrive as one JSON object per run, ready to store.
Why the Scrapeless DeepSeek Scraper
The scraper.deepseek actor belongs to the LLM Chat Scraper family inside the Universal Scraping API line:
- One prompt in, structured answer out. The login handling and the streaming reassembly happen server-side, so you never touch an interface that was not built to be parsed.
- The fragment stream is preserved. Reasoning, search queries, opened pages, and the final answer arrive as separate typed objects rather than one flattened string.
- Country-pinned residential egress. Runs route through residential proxies across 195+ countries; the required
countryinput is the entire configuration. - One contract across the family. The endpoint, the
x-api-tokenheader, and the submit-then-collect flow are identical for the ChatGPT, Gemini, Perplexity, Copilot, and Grok actors.
A note on documentation: the LLM Chat Scraper quickstart documents the shared task flow and lists the other actors in the family, but does not yet carry a DeepSeek page. Every field and flag described below was captured from live runs against the actor rather than read off a reference page.
Prerequisites
- A Scrapeless account and API key — create one at app.scrapeless.com.
curlandjqfor the quick capture, or Python 3.10+ for the client.- Familiarity with HTTP and JSON.
Keep the key in the environment so it never reaches your source tree:
bash
export SCRAPELESS_API_KEY=your_api_token_here
How the DeepSeek Scraper API works
The actor is asynchronous. You create a task, then collect it.
- Submit:
POST https://api.scrapeless.com/api/v2/scraper/request→201with{"status": "pending", "task_id": "..."} - Collect:
GET https://api.scrapeless.com/api/v2/scraper/result/{task_id}→202with{"status": "running"}while the run is in flight, then200with the full result - Auth header:
x-api-token: $SCRAPELESS_API_KEY
The 202 is doing exactly the job the HTTP semantics specification defines for it: the request was accepted, processing is not complete, and the outcome lives at a separate location. Poll that location on a fixed interval until it answers 200. Completed results are held for five minutes, so collect promptly or register a webhook instead.
Request parameters
| input field | required | type | description |
|---|---|---|---|
prompt |
yes | string | the question to send to DeepSeek |
country |
yes | string | two-letter country code for the run's residential egress, e.g. US |
thinking |
no | boolean | exposes DeepSeek's reasoning trace as a THINK fragment |
search |
no | boolean | lets the run retrieve live web sources and returns them in the payload |
Both required fields are validated on submit. Omitting country returns 400 with Key: 'deepseekParam.Country' Error:Field validation for 'Country' failed on the 'required' tag; omitting prompt returns the matching message for Prompt. Country codes follow the ISO 3166-1 alpha-2 standard.
Quick capture with curl
Submit the task, poll it to completion, and print the structural summary:
bash
TASK_ID=$(curl -sS -X POST https://api.scrapeless.com/api/v2/scraper/request \
-H "Content-Type: application/json" \
-H "x-api-token: ${SCRAPELESS_API_KEY}" \
-d '{
"actor": "scraper.deepseek",
"input": {"prompt": "Explain how HTTP caching headers work.", "country": "US"}
}' | jq -r '.task_id')
echo "task_id=${TASK_ID}"
for _ in $(seq 1 60); do
BODY=$(curl -sS -H "x-api-token: ${SCRAPELESS_API_KEY}" \
"https://api.scrapeless.com/api/v2/scraper/result/${TASK_ID}")
echo "${BODY}" | jq -e '.status == "success"' >/dev/null 2>&1 && break
sleep 4
done
echo "${BODY}" | jq -r '"status=" + .status,
"fragments=" + ([.task_result.fragments[].type] | join(",")),
"answer_chars=" + (.task_result.markdown | length | tostring),
"tokens=" + (.task_result.accumulated_token_usage | tostring)'
The response envelope
A completed collect call returns a plain JSON document, well inside what the JSON interchange format standard defines, with two top-level keys: status and task_result.
json
// illustrative sample — every key and type below is from live scraper.deepseek runs; long strings abridged
{
"status": "success",
"task_result": {
"markdown": "To handle caching, an HTTP response carries…",
"html": "<p class=\"ds-markdown-paragraph\">…</p>",
"fragments": [
{"type": "RESPONSE", "id": 2, "stage_id": 1, "content": "To handle caching…", "references": []}
],
"accumulated_token_usage": 637,
"thinking_enabled": false,
"search_enabled": false,
"search_triggered": false,
"status": "FINISHED",
"quasi_status": "FINISHED",
"role": "ASSISTANT",
"message_id": 2,
"parent_id": 1,
"conversation_mode": "DEFAULT",
"inserted_at": 1786037083.6987588,
"model": "",
"feedback": null,
"incomplete_message": null,
"auto_continue": false,
"ban_edit": false,
"ban_regenerate": false,
"has_pending_fragment": false
}
}
Field by field:
| field | type | what it holds |
|---|---|---|
task_result.markdown |
string | the answer in Markdown, per the CommonMark specification — this is the field most pipelines want |
task_result.html |
string | the same answer as rendered HTML, carrying DeepSeek's own ds-markdown-* class names |
task_result.fragments[] |
array | the ordered stream of typed steps that produced the answer; see the next section |
task_result.accumulated_token_usage |
number | tokens consumed by the run |
task_result.thinking_enabled |
boolean | echoes whether the reasoning trace was requested |
task_result.search_enabled |
boolean | echoes whether live retrieval was requested |
task_result.search_triggered |
boolean | whether retrieval actually ran |
task_result.status / quasi_status |
string | both read FINISHED on a completed run |
task_result.role |
string | ASSISTANT |
task_result.message_id / parent_id |
number | position in the conversation; a fresh run is message 2 under parent 1 |
task_result.inserted_at |
number | Unix timestamp with fractional seconds |
task_result.conversation_mode |
string | DEFAULT on every captured run |
task_result.model |
string | empty on every run captured for this guide, including runs that supplied a model input |
task_result.feedback / incomplete_message |
null | reserved; null on completed runs |
task_result.auto_continue, ban_edit, ban_regenerate, has_pending_fragment |
boolean | interface-state flags, all false on a completed run |
Treat model as unavailable rather than as a field to read. If you need to know which configuration produced a capture, record the flags you sent alongside the response.
Get your API key on the free plan: app.scrapeless.com
The fragment stream is where the detail lives
fragments is the field that separates this actor from a plain chat capture. Its length and composition change with the flags you send:
| flags sent | fragment sequence | what you gain |
|---|---|---|
| neither | RESPONSE |
the answer only |
thinking: true |
THINK, RESPONSE |
the reasoning text and its duration |
search: true |
SEARCH, RESPONSE |
the queries DeepSeek issued and every source it retrieved |
| both | THINK, TOOL_SEARCH, THINK, TOOL_OPEN × N, THINK, RESPONSE |
an agentic loop: plan, search, re-plan, open individual pages, re-plan, answer |
The fragment types carry different keys, which is the part that breaks naive parsers:
RESPONSE—content,id,stage_id,type,references. Thecontentis the answer with citation markers still embedded.THINK— the same keys pluselapsed_secs. In reasoning-only mode the trace is one long block; in agentic mode it becomes several short planning notes between tool calls.SEARCH—queries(the search strings DeepSeek composed),results(the sources),status, and acontentthat isnull. There is nostage_id.TOOL_SEARCH— the agentic-mode equivalent ofSEARCH, with astage_idadded.TOOL_OPEN—reference(a pointer back to the search fragment that surfaced the URL) and a singleresultobject for the one page that was opened. Nocontent, noreferences.
Every source object — in SEARCH.results, TOOL_SEARCH.results, and TOOL_OPEN.result — carries the same eight keys: title, url, snippet, site_name, site_icon, published_at, query_indexes, and cite_index.
An agentic run captured for this guide produced 13 fragments: an opening plan, one TOOL_SEARCH that ran four queries and returned 38 unique URLs, eight TOOL_OPEN fragments for the pages DeepSeek chose to read in full, two more planning notes, and the answer. The TOOL_OPEN set is the interesting one — those eight URLs are what DeepSeek actually read, as distinct from the 38 it merely saw.
The reasoning behavior this exposes is the same capability described in the DeepSeek-R1 reinforcement-learning paper; the actor's contribution is making the trace available as a field instead of a rendered panel.
Integrating the API in Python
A complete client: submit, poll to completion, and index the sources by their citation number.
python
# deepseek_client.py — submit a prompt to scraper.deepseek and collect the result
import os
import time
import requests
BASE = "https://api.scrapeless.com/api/v2/scraper"
HEADERS = {
"Content-Type": "application/json",
"x-api-token": os.environ["SCRAPELESS_API_KEY"],
}
def ask_deepseek(prompt, country="US", thinking=False, search=False, interval=4):
created = requests.post(
f"{BASE}/request",
headers=HEADERS,
json={
"actor": "scraper.deepseek",
"input": {
"prompt": prompt,
"country": country,
"thinking": thinking,
"search": search,
},
},
timeout=60,
)
created.raise_for_status()
task_id = created.json()["task_id"]
while True:
collected = requests.get(f"{BASE}/result/{task_id}", headers=HEADERS, timeout=120)
if collected.status_code == 200:
return collected.json()
if collected.status_code != 202:
raise RuntimeError(f"task {task_id} did not complete: {collected.text}")
time.sleep(interval)
def sources_by_citation(result):
"""Every source the run produced, keyed by the cite_index used in the answer."""
found = {}
for fragment in result.get("fragments") or []:
for source in fragment.get("results") or []:
found[source.get("cite_index")] = source
if fragment.get("result"):
found[fragment["result"].get("cite_index")] = fragment["result"]
return found
if __name__ == "__main__":
payload = ask_deepseek(
"What are the latest developments in fusion energy research?",
search=True,
)
result = payload["task_result"]
fragments = result.get("fragments") or []
cited = sources_by_citation(result)
print(f"status={payload['status']} search_triggered={result['search_triggered']}")
print("fragments=" + ",".join(f.get("type", "?") for f in fragments))
print(f"answer_chars={len(result['markdown'])} tokens={result['accumulated_token_usage']}")
print(f"sources={len(cited)}")
for index in sorted(k for k in cited if isinstance(k, int))[:3]:
source = cited[index]
print(f" [citation:{index}] {source['site_name']} -> {source['url']}")
One captured run:
text
status=success search_triggered=True
fragments=SEARCH,RESPONSE
answer_chars=7664 tokens=926
sources=11
[citation:1] Lawrence Livermore National Laboratory (.gov) -> https://lasers.llnl.gov/news/llnl-experts-help-advance-inertial-fusion-energy-us-ife-conference
[citation:2] Reuters -> https://www.reuters.com/business/energy/fusion-energy-developer-tae-signs-helium-3-future-fuel-supply-option-agreement-2026-08-05/
[citation:3] Oak Ridge National Laboratory (.gov) -> https://www.ornl.gov/news/oak-ridge-national-lab-cleveland-clinic-and-ibm-achieve-first-known-computations-fusion?utm_source=Sutor-Group-Intelligence-and-Advisory&utm_medium=daily-links&utm_campaign=Substack
Those [citation:N] labels are the same markers embedded in the answer text, so the printed lines are already a working citation index. Note the third URL as well — source URLs arrive exactly as DeepSeek encountered them, referral tracking parameters included, so normalize before you group captures by domain or deduplicate them.
Switching the call to thinking=True, search=True swaps the flat SEARCH fragment for the agentic sequence and gives you the opened-page set instead. That mode is where the reasoning trace lives, and also where the payload is least predictable — see the next two sections before you build on it.
Resolving citations
DeepSeek marks its sources inside the answer text, and the marker format depends on which flags produced the run. Two flag combinations, three encodings between them, all confirmed against live captures:
Search only. The RESPONSE fragment's content carries [citation:N] markers, where N matches the cite_index field on entries in the SEARCH fragment's results. The top-level markdown in this mode carries a second encoding of the same information: those markers already resolved into inline Markdown links. A pipeline that only needs readable prose can read markdown and skip the join entirely.
Thinking and search together. The markers become [reference:N], and N is a zero-based index into the RESPONSE fragment's references array — not a cite_index. Each entry in that array is a back-pointer of the form {"id": 5, "type": "TOOL_OPEN"} identifying the fragment that supplied the source. In this mode the top-level markdown is byte-identical to the RESPONSE content, markers and all, so the join is on you.
That second case has an honest limit worth knowing before you build a report on it. A TOOL_OPEN back-pointer resolves cleanly, because that fragment holds exactly one result and therefore exactly one URL. A TOOL_SEARCH back-pointer does not — it names a fragment holding dozens of results, so it tells you the claim came from the search step without pinning which source. In one agentic capture, 45 of 69 references pointed at TOOL_OPEN fragments and resolved to specific URLs; the remaining 24 pointed at the search fragment as a whole. Also note that cite_index is null on results inside agentic-mode fragments, so it cannot be used as a fallback there.
The practical consequence: if per-claim source attribution is the deliverable, run with search: true alone and use cite_index. If you want the reasoning trace and the list of pages actually opened, run with both flags and treat citation binding as partial.
Common data-shape problems
- Unknown input keys are accepted and ignored in silence. Sending
web_search: true,thinking_enabled: true,search_enabled: true, ormodel: "deepseek-reasoner"all returned201and produced a run with the flag unset and no warning anywhere in the payload. The working names are exactlythinkingandsearch. A correctly-named key with the wrong type behaves differently —thinking: "true"as a string returns400 invalid params— so the API validates types on keys it recognizes and drops the rest. If you are porting a ChatGPT capture script, itsweb_searchflag will vanish and every DeepSeek answer will come back without sources. - An unsupported country fails on collect, not on submit.
country: "ZZ"returns a normal201with atask_id; the failure appears on the collect call as400with{"message": "execution failed", "status": "failed"}. Validate country codes on your side rather than reading the submit status as confirmation. markdownand theRESPONSEcontent are not always the same string. In search-only modemarkdownis longer, because citation markers have been expanded into links. In every other mode the two match. Pick one field and stay with it.referencesis not a citation list. It is an array of{id, type}back-pointers, and it is empty except in agentic mode. The sources themselves live on the search and open fragments.- Fragment key sets differ by type.
SEARCHhasqueriesandresultsbut nostage_id;TOOL_OPENhasreferenceand a singularresultbut nocontent. Read fragments bytype, never by position. - Agentic mode varies far more than the flat modes. Search-only runs came back as
SEARCH, RESPONSEin every capture taken for this guide. With both flags set, consecutive captures of the identical prompt opened 8, 15, and 13 pages, answer length ranged from 3,720 to 6,707 characters, and several runs went straight fromTOOL_SEARCHtoRESPONSEwithout opening a single page. If your pipeline needs the opened-page set, treat an empty one as an ordinary outcome and read the series rather than a single run. - A task can end in a failed state, and it surfaces on the collect call. A minority of agentic runs finished as failed rather than success; the collect call then answers
400instead of200, exactly as an unsupported country does. The client above checks for a202before continuing and raises with the body on anything else, so the task id and the server's message both reach your logs. Treat a failed task as a recorded outcome in your dataset, not as something to paper over.
Companion actors
The endpoint, header, and submit-then-collect flow stay the same across the family — only the actor name and its platform-specific inputs change:
scraper.chatgpt—promptpluscountry, with aweb_searchflag of its own.scraper.gemini— the same two-field input, returning the answer plus a citations array.scraper.perplexity— requiredcountryand aweb_searchflag; returns web results and related prompts.scraper.grok— requires a reasoningmodeand returns open-web and X citations as separate arrays; covered in the Grok scraper API guide.scraper.copilotandscraper.alexa— the Copilot and Alexa answer surfaces under the same contract.
Because each actor names its flags differently, keep the input builder per-actor rather than sharing one dictionary across them. Usage-based pricing for the line, with free trial credits on signup, is on the pricing page.
Conclusion: two calls, and a payload worth reading closely
Capturing DeepSeek is two HTTP calls: POST { actor: "scraper.deepseek", input: { prompt, country } } to create a task, then GET the result until it answers 200. The answer is in markdown. Everything that makes DeepSeek distinct — the reasoning trace, the search queries, the pages it opened — is in fragments, and only if you asked for it with thinking and search. Name those two flags exactly, because the API will accept anything else you send and quietly ignore it.
Ready to Capture DeepSeek Answers as Data?
Join our community to claim a free plan and compare notes with developers building AI-answer pipelines: Discord · Telegram.
Sign up at app.scrapeless.com for free trial credits, and point scraper.deepseek at the prompts and markets your monitoring program covers.
FAQ
Q: How do I authenticate a DeepSeek scraper API request?
Every call carries the header x-api-token: <your key>, on both the submit and the collect request. One account key covers scraper.deepseek and every other Scrapeless actor. Create a key on the free plan at app.scrapeless.com.
Q: Why does the request return a task_id instead of the answer?
The actor is asynchronous, so a submit returns 201 with {"status": "pending", "task_id": "..."} and the answer arrives from a second call to /api/v2/scraper/result/{task_id}. That endpoint returns 202 with {"status": "running"} until the run finishes, then 200 with the full task_result. Completed results are held for five minutes; a webhook URL is the alternative to polling.
Q: How do I get DeepSeek's reasoning trace?
Send "thinking": true in the input. The response then contains a THINK fragment whose content is the reasoning text and whose elapsed_secs is the time spent on it. Without that flag the reasoning is not generated and thinking_enabled comes back false.
Q: Does the DeepSeek scraper return sources and citations?
Yes, when you send "search": true. Sources arrive as objects with title, url, snippet, site_name, site_icon, published_at, query_indexes, and cite_index, attached to the SEARCH or TOOL_SEARCH fragment. Without the flag, no sources are retrieved and the answer is generated from the model alone.
Q: Why is my web_search parameter having no effect?
Because that is the ChatGPT actor's parameter name. DeepSeek's flag is search, and unknown keys are accepted with a 201 and dropped without an error. The same applies to thinking_enabled, search_enabled, and model — use exactly thinking and search.
Q: Which fields are empty or nullable?
model came back as an empty string on every run captured for this guide, feedback and incomplete_message are null on completed runs, and references is empty unless the run used both flags. search_triggered stays false when search was not requested. Read defensively and treat absent fields as absent rather than as failures.
Q: Can I run this without an SDK?
Yes. It is plain HTTP — curl, Python requests, Node fetch, or any client that can send a JSON POST and a GET with one header.
Q: Is capturing DeepSeek answers legal?
The actor captures publicly generated answer content, but rules vary by jurisdiction and by platform terms of service. Review the applicable terms and consult counsel for your use case, particularly before redistributing captures, and do not collect personal data protected under GDPR or CCPA. When your pipeline goes on to fetch the source URLs DeepSeek cites, honour the crawler directives standardized by the Robots Exclusion Protocol on each of those sites as well.
At Scrapeless, we only access publicly available data while strictly complying with applicable laws, regulations, and website privacy policies. The content in this blog is for demonstration purposes only and does not involve any illegal or infringing activities. We make no guarantees and disclaim all liability for the use of information from this blog or third-party links. Before engaging in any scraping activities, consult your legal advisor and review the target website's terms of service or obtain the necessary permissions.



