No KYC — no ID, no selfie, no documents. Pay in crypto: BTC, USDT, USDC, ETH, SOL, LTC
Language: English
Get an API key

Docs · Python

Python examples: search, run queries in parallel, answer with citations

Three scripts cover what most Python agents need: one search with requests, many searches at once with httpx and a semaphore, and a short loop that hands the sources to a language model and gets back an answer with numbered citations.

Last updated: · 5 min read

Quick answer

How do I call a web search API for AI agents from Python?

POST the query to https://llmwebsearch.com/api/v1/search with requests or httpx and a Bearer key. LLMWebSearch returns ranked results with the top 3 pages as Markdown, which you can number and pass to any LLM to answer with citations. One search costs 1 credit, from $1.20/1K searches as of October 2026. Python 3.9+ and no SDK. No KYC; pay in crypto.

What you need

Python3.9 or newer
Packagesrequests for the simple script, httpx for the async one
KeyLLMWEBSEARCH_API_KEY in your environment
For the answer loopAny OpenAI-compatible chat endpoint: LLM_BASE_URL, LLM_API_KEY, LLM_MODEL
BillingOne credit pays for one search: up to 20 ranked results, with the pages of the first 3 read and returned as Markdown. Reading 4 to 10 pages makes it 2 credits. A call that ends in an error costs nothing

How do I run one search with requests?

Build a JSON body with q and the options you need, post it, and loop over results. Check content_status before touching content: only results marked ok carry page text. The script below saves each page that was read as a Markdown file named after its position, with the source URL on the first line.

search_once.py
import os
import pathlib

import requests

resp = requests.post(
    "https://llmwebsearch.com/api/v1/search",
    headers={"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]},
    json={"q": "how to size a postgres connection pool", "max_results": 5, "read": 3},
    timeout=60,
)
resp.raise_for_status()
data = resp.json()
out = pathlib.Path("sources")
out.mkdir(exist_ok=True)
for r in data["results"]:
    if r["content_status"] != "ok":
        print("skip", r["position"], r["content_status"], r["url"])
        continue
    path = out / f"{r['position']:02d}.md"
    path.write_text("Source: " + r["url"] + "\n\n" + r["content"], encoding="utf-8")
    print("saved", path, r["word_count"], "words")
print("credits used:", data["credits_used"])

How do I run many searches at once?

Use httpx.AsyncClient and an asyncio.Semaphore. The semaphore caps how many calls are in flight, which keeps you under your account's per-minute limit and spreads the load. Four to eight concurrent calls suit most research agents. Every account has a per-minute call limit, shown in the dashboard; ask support and it can be raised.

The example sends one search per sub-question, a typical step of a research agent, and collects the URLs of the pages that were read. Each call is independent: one failure does not stop the others.

many_searches.py
import asyncio
import os

import httpx

API = "https://llmwebsearch.com/api/v1/search"
HEADERS = {"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]}
QUESTIONS = [
    "what is connection pooling in postgres",
    "recommended pool size per cpu core",
    "pgbouncer transaction mode limitations",
    "connection pool exhaustion symptoms",
]


async def search(client, sem, q):
    async with sem:
        resp = await client.post(API, json={"q": q, "read": 2, "max_chars": 4000})
        if resp.status_code != 200:
            return q, resp.json().get("error", {}).get("code"), []
        data = resp.json()
        read = [r["url"] for r in data["results"] if r["content_status"] == "ok"]
        return q, "ok", read


async def main():
    sem = asyncio.Semaphore(4)
    async with httpx.AsyncClient(headers=HEADERS, timeout=60) as client:
        for q, status, urls in await asyncio.gather(*(search(client, sem, q) for q in QUESTIONS)):
            print(status, q, urls)


asyncio.run(main())

How do I get an answer with citations from any LLM?

Search first, then give the model numbered sources and ask it to cite them. The API returns sources, not a written answer: your own model reads the pages, writes the reply and cites the URLs, so you choose the model and the citation style. The loop below works with any chat endpoint that speaks the OpenAI-compatible chat/completions format; set its address, key and model in three environment variables.

Keep the context under control. The script trims each source so the total stays within a budget of about 6,000 tokens, counting roughly four characters per token. A result without content is passed as its snippet, labelled as such, so the model does not pretend it read the page.

answer_with_citations.py
import os

import requests

SEARCH = "https://llmwebsearch.com/api/v1/search"
KEY = os.environ["LLMWEBSEARCH_API_KEY"]
BUDGET_CHARS = 6000 * 4  # about 6,000 tokens of sources


def sources_for(question):
    resp = requests.post(SEARCH, headers={"Authorization": "Bearer " + KEY},
                         json={"q": question, "max_results": 5, "read": 3}, timeout=60)
    resp.raise_for_status()
    results = resp.json()["results"]
    share = BUDGET_CHARS // max(len(results), 1)
    blocks = []
    for i, r in enumerate(results, start=1):
        if r["content_status"] == "ok":
            body = r["content"][:share]
        else:
            body = "(snippet only) " + r["snippet"]
        blocks.append(f"[{i}] {r['title']} - {r['url']}\n{body}")
    return "\n\n".join(blocks)


def answer(question):
    prompt = ("Answer the question using only the sources below. "
              "Cite them as [1], [2] and so on. Say so if the sources do not answer it.\n\n"
              + sources_for(question) + "\n\nQuestion: " + question)
    resp = requests.post(
        os.environ["LLM_BASE_URL"].rstrip("/") + "/chat/completions",
        headers={"Authorization": "Bearer " + os.environ["LLM_API_KEY"]},
        json={"model": os.environ["LLM_MODEL"], "messages": [{"role": "user", "content": prompt}]},
        timeout=120,
    )
    resp.raise_for_status()
    return resp.json()["choices"][0]["message"]["content"]


print(answer("How should I size a Postgres connection pool?"))

How much text should I send to the model?

As little as answers the question. Three pages trimmed to 2,000 tokens each usually beat ten full pages: the model focuses better and each answer costs less. Lower max_chars in the search itself when you know you will trim, so less data travels.

Trimming from the start of a page works for most articles, because the main point tends to come first. For long reference pages, split the content by headings and keep the sections whose titles match the question.

What should the code do when a page is missing?

Keep going with the other sources. A page marked blocked_by_robots, blocked_by_site_policy or target_refused will not be read on a second try, so do not loop on it. A fetch_timeout or fetch_failed page may work later through https://llmwebsearch.com/api/v1/extract.

HTTP errors are different: 429, 502 and 504 deserve a retry with a growing pause. The errors page has a ready retry helper.

Python: common questions

Is there a pip package for LLMWebSearch?

No. The API is plain HTTPS and JSON, so requests or httpx is all you need. The scripts on this page are the integration: copy them into your project and adapt them.

Can I use these scripts with a local model?

Yes. Any server that exposes an OpenAI-compatible chat/completions route works with the citation loop: set LLM_BASE_URL to its address and LLM_MODEL to the model name it expects.

How do I count credits from Python?

Add up credits_used from each response, or call GET https://llmwebsearch.com/api/v1/credits before and after a job; that call is free.

From $1.20/1K searches · no KYC

Get an API key