Back to use cases

    Use case / DEVELOPERS / Python 3 / Published

    Build an AI Search Engine Web App with Dates and Passages in Python

    A small search box for your product, docs or agent: ranked web pages with published dates and the passage that matches, plus a JSON endpoint for agents.

    Card for Build an AI Search Engine Web App with Dates and Passages in Python

    Made by Desearch (desearch.ai).

    This is a small, self-hosted search box for a product, a docs site, or an agent. Type a search, pick a date range, and get ranked web pages, each with its published date when the page has one, and the passage that best matches the search. An optional Answer button adds a short answer that is shown only next to the links it cites. It is one HTML page, two JSON endpoints (GET /api/search and GET /api/answer), and a CLI. Standard library only, plain HTTP.

    The problem

    When you add search to a product or an agent, a list of links is not enough. People want to see when a page was published and which part of it matches the search before they click. Building that yourself means crawling, ranking, date parsing and passage extraction. This app uses one search call that returns pages you can show and cite, and an optional second call for a short answer.

    What the app does

    • Takes a search (q, 2 to 300 characters, at least one letter or number), a mode (fast or balanced), a result count (10, 15 or 20) and a date range.
    • The range is any, day, week, month or year (counted back from today, UTC), or explicit from and to dates (YYYY-MM-DD). The page also offers custom dates, which send from and to.
    • Returns ranked pages with title, domain, link, published date and the best-matching passage (highlights on POST /search).
    • Labels a page with no published date as "date not given", and a dated page outside the window as "outside date window".
    • Shows results in the order the API returned, and also offers "newest first".
    • Optional Answer step: POST /desearch/ai/search with tools set to web, streaming false, result_type LINKS_WITH_FINAL_SUMMARY and count 10.
    • Serves the same search and answer through GET /api/search and GET /api/answer for agents, and through a CLI.

    The README notes that some results have no published date (1 to 2 of 10 on evergreen queries). With a date window set, every result in those runs was dated inside it.

    How it works

    1. Check the input. A blank search, a search under 2 or over 300 characters, a search with no letter or number, a bad mode or count, or a from date after to returns HTTP 400 and makes no paid call. /api/answer takes q only.
    2. Search. search_body in searchapp/core.py posts query, mode, count and highlights: true, plus start_date and end_date when they are set. The API filters on the page's published date. any sends no dates.
    3. Clean the results. Keep the API's order, drop duplicate URLs, take the first highlight, collapse whitespace and cut it at about 420 characters, and flag a missing date or a date outside the window. Passages are otherwise shown as returned.
    4. Answer (optional). answer_body sends prompt, tools: ["web"], streaming: false, result_type: "LINKS_WITH_FINAL_SUMMARY" and count: 10. The app keeps a sentence only when every citation points at a link that same answer call returned. If the service sends its fixed decline reply, the page shows that text and no summary lines.
    5. Serve. The HTML page and the JSON endpoints share searchapp/service.py. Result responses send Cache-Control: public, max-age=0, s-maxage=21600, so a CDN can answer a repeat for 6 hours. A cached response costs nothing.

    The code

    The key is read from --key-file, DESEARCH_API_KEY_FILE, or DESEARCH_API_KEY. It is sent only in the Authorization header.

    The README's first call:

    BASH
    curl -s https://api.desearch.ai/search \
      -H "Authorization: $DESEARCH_API_KEY" -H "Content-Type: application/json" \
      -d '{"query": "open source LLM released this week", "mode": "fast", "count": 10, "start_date": "2026-10-12", "end_date": "2026-10-18"}'

    That example does not set highlights. The app does. search_body in searchapp/core.py (lines 94 to 101):

    PYTHON
    def search_body(q, mode="fast", count=10, start=None, end=None):
        """POST /search body (live OpenAPI 2026-10-19). highlights=true returns the best-matching passage."""
        b = {"query": q, "mode": mode, "count": count, "highlights": True}
        if start:
            b["start_date"] = start
        if end:
            b["end_date"] = end
        return b

    The Answer body, from the same file (lines 104 to 106):

    PYTHON
    def answer_body(q):
        """POST /desearch/ai/search body for the Answer button: web only, one JSON response with links + completion."""
        return {"prompt": q, "tools": ["web"], "streaming": False, "result_type": "LINKS_WITH_FINAL_SUMMARY", "count": 10}

    searchapp/client.py sends the body with plain HTTP and sets a User-Agent (lines 39 to 42):

    PYTHON
            hdr = {"Authorization": self.key, "User-Agent": UA}
            if data is not None:
                hdr["Content-Type"] = "application/json"
            req = urllib.request.Request(url, data=data, method=method, headers=hdr)

    The README says a Python urllib call without a User-Agent is blocked with HTTP 403 (error code: 1010) before it reaches the API.

    Files, from the README:

    • searchapp/core.py: input checks, request bodies, result cleanup, citation check, limiter, cap and cache. No network.
    • searchapp/client.py: plain-HTTP client (User-Agent, key redaction guard, cost from the header or the body).
    • searchapp/service.py: the search and answer logic shared by the local server and the hosted functions.
    • api/search.py, api/answer.py, public/index.html, vercel.json: the hosted version.
    • app.py: local server and CLI. test_core.py: offline tests. samples/: a saved run.

    View the code

    Endpoints

    • POST /search
    • POST /desearch/ai/search

    Example output

    Saved sample in sample-output.md and samples/python-314-free-threading.json, from this command at 2026-10-19 09:31 Asia/Tbilisi:

    BASH
    python3 app.py --cli "python 3.14 free threading performance" --range year --answer

    The search used fast mode and 10 results, with start_date 2025-10-19 and end_date null. The CLI prints that window as from 2025-10-19 to today. Cost $0.005, 0.48 s. The saved file reports returned 10, shown 8, duplicates 0, no date 0 and outside the window 0. Results whose URL contains a personal handle (2 of 10) are left out of the saved sample.

    RankDomainPublished
    1compare.iprice.ph2025-10-25
    2youtube.com2025-11-10
    3stackademic.com2026-09-18
    4byteiota.com2026-07-03
    5dev.to2026-04-13
    6tech.yahoo.com2026-10-17
    7c-sharpcorner.com2026-10-12
    8byteiota.com2026-08-13

    Each row in the app also shows the page title and its matching passage. The first result's passage begins: "Early benchmarks show dramatic improvements in request processing rates for certain workloads".

    The answer call cost $0.004 and took 9.8 s. It returned 10 links. The saved run sets declined to true, and the text it shows is: "The sources do not provide specific information about Python 3.14's performance with threading." 2 of 10 answer links also appear in the search results.

    Run it yourself

    Clone desearch-py-examples and open ai-search-engine-web-app. Then run the README's local commands:

    BASH
    python3 -m pip install -r requirements-dev.txt   # only pytest, for the tests
    export DESEARCH_API_KEY_FILE=/path/to/key         # or DESEARCH_API_KEY=your_key, or --key-file
    python3 app.py                                    # web app at http://127.0.0.1:8000

    The README's one-off command:

    BASH
    python3 app.py --cli "python 3.14 free threading performance" --range year --answer --json out.json
    • --range day|week|month|year limits results by published date. from and to on /api/search set explicit dates instead. The default range is any.
    • --answer adds the cited answer. That is one more call.
    • --json out.json saves the full result.

    Local checks that hit the app, from the same README block:

    BASH
    curl -s "http://127.0.0.1:8000/api/search?q=python+3.14+free+threading&range=year"
    curl -s "http://127.0.0.1:8000/api/answer?q=python+3.14+free+threading"

    What it costs

    In the runs recorded on 2026-10-19: $0.005 per search (POST /search, 10 results, either mode) and $0.004 per answer (POST /desearch/ai/search, web, count 10). Cached requests cost nothing. The README states that the default $1.00 daily cap allows up to 200 uncached searches a day. On the hosted version, search and answer are separate functions, and each has its own $1.00 cap. See pricing.

    Ideas to extend it

    • Add your site name to the search when you want pages from your own docs or blog.
    • Set start_date to the date of the user's last search for a "since my last visit" view.
    • Give an agent GET /api/search so it can cite dated pages.
    • Save each search and its passages when you want a small set for checking your own ranking.

    Use with AI agents

    Point an MCP client at the hosted Desearch server. Replace the placeholder with a key from the console. Do not commit the key.

    JSON
    {
      "mcpServers": {
        "desearch": {
          "url": "https://mcp.desearch.ai/mcp",
          "headers": {
            "Authorization": "Bearer <DESEARCH_API_KEY>"
          }
        }
      }
    }

    Prompt you can paste for this example:

    Use Desearch POST /search for the query I give you. Set mode to fast or balanced, count to 10, 15 or 20, and highlights to true. Add start_date and end_date as YYYY-MM-DD when I give a date range. Return each page's title, url, domain, published date and the first highlight as the passage. Label a missing date as date not given. If I ask for an answer, call POST /desearch/ai/search with tools set to web, streaming false, result_type LINKS_WITH_FINAL_SUMMARY and count 10. Keep a sentence only when every citation points at a link from that same answer call. Do not invent pages, dates or costs.

    Build your own example.

    Give your agents access to web and social search. Get an API key to start building, or talk to us about your integration.

    Read docs