Short answer: DuckDuckGo doesn't offer a web search API. Its only official API, the Instant Answer API, returns topic summaries, not web results. What most people mean by "the DuckDuckGo search API" is the free, unofficial ddgs Python library (formerly duckduckgo-search), which reads DuckDuckGo's result pages the way a browser would. It needs no key. It also has no published limits and no SLA, and it fails with RatelimitException: 202 Ratelimit whenever DuckDuckGo decides you're a bot. Great for a weekend prototype; for an agent in production, move to an API with a real quota.
| Option | Official? | Web results? | Limits | Cost |
|---|---|---|---|---|
| DuckDuckGo Instant Answer API | Yes | No, summaries and definitions only | Not published | Free |
ddgs / duckduckgo-search library |
No | Yes | Not published; throttled on bursts | Free |
| browserfoundry | Yes | Yes, ranked JSON | Published per plan: 10 a minute free, up to 200 on paid plans | 1,250 free credits a month, then from $30 |
| Brave Search API | Yes | Yes | Published per plan | $5 of credit a month, then $5 per 1,000 |
| Serper | Yes | Yes (Google results) | Published | 2,500 free queries once, then about $1 per 1,000 |
Does DuckDuckGo have an API?
It has one, and it's not the one you want. The Instant Answer API at api.duckduckgo.com returns DuckDuckGo's "zero-click" information: an abstract from a source like Wikipedia, a definition, a list of related topics. Ask it for "best python web framework" and you won't get ten blue links, because it was never a web results endpoint.
import requests
r = requests.get("https://api.duckduckgo.com/", params={"q": "python", "format": "json"}, timeout=10)
data = r.json()
print(data["AbstractText"]) # a summary, if DuckDuckGo has one
print(len(data["RelatedTopics"])) # related topics, not web results
Try a query with no Wikipedia-style answer and you'll get an almost empty JSON object back. That's not a bug; that's the whole product.
DuckDuckGo doesn't sell access to its web results. There's no paid tier, no enterprise plan, no form to fill in.
The library everyone actually uses: ddgs
So the internet did what the internet does and wrote a scraper. To get DuckDuckGo results in code, almost everyone uses the open-source ddgs package:
# pip install ddgs
from ddgs import DDGS
with DDGS() as ddgs:
for r in ddgs.text("open source llm benchmarks", max_results=10):
print(r["title"], r["href"], r["body"])
Two things to know:
- It was renamed. The project was
duckduckgo-searchuntil 2025, when it becameddgs. The old package stopped getting updates, so anything pinned toduckduckgo-search==x.ymisses every fix for DuckDuckGo's changes since. If your traceback saysfrom duckduckgo_search import DDGS, upgrade before debugging anything else. - It is unofficial. It requests DuckDuckGo's pages the way a browser would. DuckDuckGo can change those pages, or slow down traffic it thinks is automated, whenever it likes, and owes you nothing.
LangChain's DuckDuckGoSearchRun and DuckDuckGoSearchResults, and the "free web search" default in plenty of agent frameworks, are built on this library. They inherit every one of its limits.
What are the rate limits?
DuckDuckGo publishes none, so anyone who quotes you a "safe" number is guessing. What developers report in issue threads for Open WebUI, LangChain's OpenGPTs and others is consistent:
- Bursts get throttled. A few queries in quick succession, or several agents searching at once, trigger the error.
- Some IPs are throttled almost immediately. Shared cloud IPs and VPN exits can hit
202 Ratelimiton the very first request. Your laptop works; your server doesn't. Classic. - Waiting doesn't always clear it. People report the error persisting even with pauses of several seconds between queries.
Why RatelimitException: 202 Ratelimit happens
DuckDuckGo's results load in two steps. The first request returns a per-query token (vqd), and the request that fetches the actual results has to send it back. The library does that handshake for you, so each "search" is really more than one request. When DuckDuckGo decides the traffic looks automated, it answers with HTTP 202 and no results, and the library raises RatelimitException.
It's not a bug in your code, and no setting turns it off. It's DuckDuckGo politely asking you to go away.
Gotcha. In an agent, this error is often swallowed by the framework. The search returns nothing, the model shrugs and answers from memory, and nobody notices that your "web-grounded" agent hasn't touched the web in a week.
How to make ddgs hold up longer
If you're staying on DuckDuckGo for a prototype, these help:
- Upgrade to the current
ddgsrelease. - Reuse one
DDGS()instance instead of creating a new one per query. Users in the Open WebUI thread found this throttled less. - Search one at a time. Cap your agent at one search in flight.
- Back off and retry, with growing waits and some randomness:
import random, time
from ddgs import DDGS
from ddgs.exceptions import RatelimitException
ddgs = DDGS()
def search(query, tries=4):
for attempt in range(tries):
try:
return list(ddgs.text(query, max_results=10))
except RatelimitException:
time.sleep(2 ** attempt + random.random()) # 1s, 2s, 4s, 8s, plus jitter
return []
- Cache results. Agents repeat themselves far more than you'd expect. Even a tiny cache helps:
from functools import lru_cache
@lru_cache(maxsize=2048)
def cached_search(query):
return tuple(search(query))
- Proxies spread requests over more IPs, at the price of paying for proxies, more failures and more moving parts. Once you're paying for proxies to scrape a free service, a paid API is almost always cheaper and certainly calmer.
How to tell your agent is silently failing
Add one line of logging and you'll know:
results = search(query)
if not results:
log.warning("empty search", extra={"query": query}) # alert if this spikes
If empty results go from rare to common, you're being throttled. That's your sign.
When to stop using DuckDuckGo
The moment an agent, or anything a customer depends on, runs on it. Free is great until the cost is wrong answers nobody can trace. A search API gives you a published quota, a key, errors you can monitor, and results designed for software to read.
| Situation | Stay on ddgs? |
|---|---|
| Notebook, hackathon, personal script | Sure |
| Internal tool, a few searches a day | Probably, with retries and caching |
| Agent serving users | No |
| Anything running in parallel | No |
| Anything on a cloud server IP | Expect pain |
Moving an agent to a search API
Replacing ddgs with browserfoundry is a small change:
import os, requests
def search(query, n=10):
r = requests.post(
"https://api.keirolabs.cloud/api/v2/search/fast",
headers={"Authorization": f"Bearer {os.environ['KEIRO_API_KEY']}"},
json={"query": query, "maxResults": n},
timeout=30,
)
r.raise_for_status()
# same shape as ddgs results: title, href, body
return [{"title": x["title"], "href": x["url"], "body": x["snippet"]} for x in r.json()["results"]]
The function keeps ddgs's field names (title, href, body), so the rest of your code doesn't change. If you use LangChain, wrap it in a @tool and swap it in where DuckDuckGoSearchRun was.
Real failures now raise real HTTP errors you can catch, retry and alert on, instead of an empty list.
Free tiers, compared
| Provider | Free allowance | Card needed | What it covers |
|---|---|---|---|
| browserfoundry | 1,250 credits a month (1,250 fast searches) | No | search, plus extraction and browsers on paid plans |
| Brave Search API | $5 of credit a month (about 1,000 searches) | Yes | search; attribution required for the credit |
| Serper | 2,500 queries, one time | No | Google results |
| SerpApi | 250 searches a month | No | Google and other engines |
If you're choosing a search tool for an LLM agent, the other half of the decision is whether to use the model's own built-in search instead: see what web search costs inside the OpenAI, Claude, Gemini and Perplexity APIs. And if you came here from the Google side, Google's Custom Search API is shutting down too.
How we put this together
The library behaviour comes from the ddgs project, DuckDuckGo's own Instant Answer API, and the public issue threads linked above, where developers describe the rate-limit error and what helped. Free tiers and prices come from each provider's published pages in October 2026: Brave Search API, Serper, SerpApi, and browserfoundry's pricing and API docs. DuckDuckGo publishes no limits, so no number in this post is a DuckDuckGo limit.
FAQ
Does DuckDuckGo have a search API?
Not for web results. DuckDuckGo's only official API is the Instant Answer API at api.duckduckgo.com, which returns topic summaries and definitions, not a list of web results. Libraries such as ddgs read DuckDuckGo's result pages instead.
Is the DuckDuckGo search API free?
The ddgs library is free and needs no key, but it is unofficial: DuckDuckGo publishes no limits, offers no SLA, and throttles traffic it sees as automated, which is what the 202 Ratelimit error means.
Why do I get RatelimitException: 202 Ratelimit?
DuckDuckGo is throttling your requests. It happens with bursts of queries, many concurrent agents, shared cloud IPs and VPNs, and sometimes on a single request from a flagged IP.
What is the difference between duckduckgo-search and ddgs?
They are the same project. The package was renamed from duckduckgo-search to ddgs in 2025, and the old package stopped receiving updates, so code pinned to it misses fixes.
What should an agent use instead of DuckDuckGo?
A search API with a published quota. Free tiers include browserfoundry (1,250 credits a month, no card), Brave Search ($5 of credit a month, card required) and Serper (2,500 one-time queries).