> SpecStream is the live web data engine for teams and agents. This is the Markdown copy of https://specstream.ai/docs/integrations/stirrup. Every page, with what SpecStream does and costs: https://specstream.ai/llms.txt

# Stirrup

Stirrup's built-in `web_search` calls the Brave Search API and needs `BRAVE_API_KEY`. Pass a SpecStream `Tool` in the Agent's `tools` argument and the same `web_search` name runs on the SpecStream Search API instead: one Python file, no fork, and a direct answer with its page on top of the results.

[SpecStream for StirrupAgent frameworks & builders](https://specstream.ai/integrations/stirrup.md)

## What you need

-   Python and Stirrup 0.2.1 or later (`pip install stirrup`). The MCP option below also needs `pip install 'stirrup[mcp]' 'mcp<2'`.
-   A SpecStream account. New accounts get $10 of credit on sign-up, no card. [Create one](https://specstream.ai/signup?utm_source=stirrup).

1.  1

    ## 1. Create an API key

    In your SpecStream workspace, open [Agents & API](https://specstream.ai/workspace/agents?tab=keys&utm_source=stirrup), go to the **API keys** tab, and click **Create API key**. Keep the permissions that are already on, including **Authorize paid work**, and click **Set a total limit** to cap what this key can spend. Copy the key from the next screen. SpecStream shows it once.

    Set it in the environment where the agent runs. Leave `BRAVE_API_KEY` unset: without it, Stirrup's `WebToolProvider` returns only its page fetch tool, so the SpecStream tool is the one named `web_search`.

    Terminal

    ```bash
    export SPECSTREAM_API_KEY="ss_live_..."
    unset BRAVE_API_KEY
    ```

2.  2

    ## 2. Define the SpecStream search tool

    A Stirrup `Tool` is a name, a description, a Pydantic parameters model and an executor, and the executor can be async. This one sends the query to `POST https://api.specstream.ai/v1/web-search` and returns XML in the same form as Stirrup's Brave tool: one `<result>` with `<title>`, `<url>` and `<description>` for each page. When SpecStream finds one answer to the question, an `<answer>` with the value and the page that prints it comes first.

    Stirrup already depends on `httpx` and `pydantic`, so the file needs nothing new installed:

    specstream\_tools.py

    ```python
    import os
    from html import escape
    
    import httpx
    from pydantic import BaseModel, Field
    
    from stirrup import Tool, ToolResult, ToolUseCountMetadata
    
    SPECSTREAM_API = "https://api.specstream.ai"
    
    
    def _headers() -> dict[str, str]:
        return {"Authorization": f"Bearer {os.environ['SPECSTREAM_API_KEY']}"}
    
    
    class WebSearchParams(BaseModel):
        query: str = Field(description="The question or search terms, in plain words. site:, quotes and -word work.")
    
    
    async def web_search(params: WebSearchParams) -> ToolResult[ToolUseCountMetadata]:
        async with httpx.AsyncClient(timeout=60) as client:
            response = await client.post(
                f"{SPECSTREAM_API}/v1/web-search",
                headers=_headers(),
                json={"q": params.query},
            )
        if response.status_code != 200:
            return ToolResult(
                content=f"<error>{escape(response.text[:2000])}</error>",
                success=False,
                metadata=ToolUseCountMetadata(),
            )
        data = response.json()["data"]
        lines = ["<results>"]
        answer = data.get("answer")
        if answer:
            lines.append(
                f"<answer><value>{escape(str(answer.get('value') or ''))}</value>"
                f"<url>{escape(str(answer.get('page') or ''))}</url></answer>"
            )
        for source in data.get("sources", []):
            lines.append(
                f"<result><title>{escape(source.get('title') or '')}</title>"
                f"<url>{escape(source['url'])}</url>"
                f"<description>{escape(source.get('snippet') or '')}</description></result>"
            )
        lines.append("</results>")
        return ToolResult(content="\n".join(lines), metadata=ToolUseCountMetadata())
    
    
    WEB_SEARCH_TOOL = Tool(
        name="web_search",
        description="Search the live web with SpecStream. Returns a direct answer with its page when one is found, then the results with title, URL and description as XML.",
        parameters=WebSearchParams,
        executor=web_search,
    )
    ```

3.  3

    ## 3. Pass it in the Agent's tools argument

    `default_tools()` is `LocalCodeExecToolProvider()` and `WebToolProvider()`. List those two yourself and add `WEB_SEARCH_TOOL`. The agent keeps `code_exec` and `fetch_web_page`, and its `web_search` calls now go to SpecStream:

    agent.py

    ```python
    import asyncio
    
    from stirrup import Agent
    from stirrup.clients.chat_completions_client import ChatCompletionsClient
    from stirrup.tools import LocalCodeExecToolProvider, WebToolProvider
    
    from specstream_tools import WEB_SEARCH_TOOL
    
    
    async def main() -> None:
        client = ChatCompletionsClient(
            base_url="https://openrouter.ai/api/v1",
            model="anthropic/claude-opus-5",
            max_tokens=8_192,
            context_window_tokens=1_000_000,
        )
        agent = Agent(
            client=client,
            name="researcher",
            max_turns=15,
            tools=[LocalCodeExecToolProvider(), WebToolProvider(), WEB_SEARCH_TOOL],
        )
        async with agent.session(output_dir="./output/research") as session:
            finish_params, history, metadata = await session.run(
                "What is the population of Australia over the last 3 years? Search the web and cite each page you use."
            )
            print(finish_params)
    
    
    if __name__ == "__main__":
        asyncio.run(main())
    ```

4.  4

    ## 4. Run it

    Run `python agent.py`. The `metadata` that `session.run` returns counts the `web_search` calls under that name, as it did with Brave. Every charged call is kept in your account's search history, with its query, answer and price.

## What changes from Brave

Stirrup's own tool asks Brave for 5 results and returns each one's title, URL and description. The SpecStream tool returns its results in the same tags, and adds the answer and the page it was read from when the question has one. Search operators in the query hold on every result: `site:`, `-site:`, `-word`, `"phrase"`, `filetype:`, and `after:` or `before:` with a date. A query in another language is searched in that language.

The Search API scores 97.9 F-score on all 1,000 SimpleQA Verified questions and 64.0 item F1 on all 200 WideSearch tasks. Scores and method are on [Benchmarks](https://specstream.ai/benchmarks.md).

## Read pages with the Extract API

Stirrup's `fetch_web_page` reads a page from your own machine with one plain request. To read pages through SpecStream instead, add this tool to `specstream_tools.py`. It sends up to 20 URLs to `POST https://api.specstream.ai/v1/extract` and returns each page's main text as markdown. A page that refuses a plain read is tried as a browser would ask for it, then in a real browser, then from the site's own feed, then from its newest public archive capture. PDF, Word and Excel files come back as text too.

## Use both tools without WebToolProvider

With the Extract API reading pages, leave `WebToolProvider` out:

## Or connect the SpecStream MCP server

Install the extra with `pip install 'stirrup[mcp]' 'mcp<2'`. Stirrup 0.2.1 imports `streamablehttp_client`, which the 2.x releases of the `mcp` package no longer have, so `stirrup.tools.mcp` fails to import on them. `MCPToolProvider` connects to a remote server over Streamable HTTP when the config has a `url`. Build the config in code so the key comes from the environment. Each tool is named after the server, so the agent gets `specstream__search_web`, `specstream__read_pages`, and the Deep Search tools that return every match on the live web as structured rows.

Deep Search quotes the most a request can cost, and nothing runs until that maximum is approved. Add `USER_INPUT_TOOL` so the agent asks you before it approves one:

## Cost

Each `web_search` call is one Search API call at $5 per 1,000 searches of up to 10 results. Each page read is one Extract API page at $0.80 per 1,000 pages, and a page with no text is not charged. Both come out of your balance with no quote and no approval, and a call that returns nothing costs nothing. New accounts start with $10 of credit, no card. To search at a fifth of the price, add `"speed": "fast"` to the JSON body: the results come back without the answer. [Get your key](https://specstream.ai/workspace/search?utm_source=stirrup&utm_medium=howto) on the Search API screen of your workspace, and read [Balance, quotes, and billing](https://specstream.ai/docs/billing.md) for the details.

## When a call fails

-   `401`: no key was sent, or the key is unknown, expired or revoked. Check `SPECSTREAM_API_KEY`.
-   `402`: the balance is empty, or the call would pass the account's monthly limit.
-   `403`: the call would pass the key's spending limit, or the key cannot spend. Raise the limit under **Agents & API**.
-   `503` with `Retry-After`: wait that many seconds and send it again. Nothing is charged.
-   The tool returns the error body with `success=False`, so the agent sees the reason and can try again.

## Remove

Take `WEB_SEARCH_TOOL` out of `tools`, or go back to `tools=default_tools()`, and set `BRAVE_API_KEY` again. To stop the key everywhere, delete it under **Agents & API**. Other tools to connect are in [Connect your tools](https://specstream.ai/docs/integrations.md). Questions go to [support@specstream.ai](mailto:support@specstream.ai).

## More

[SpecStream for Stirrup](https://specstream.ai/integrations/stirrup.md) covers what it does in Stirrup on one page. Every other tool has its guide in [Connect your tools](https://specstream.ai/docs/integrations.md). You see the most a request can cost before it starts, and the matches are paid from your balance. [Billing](https://specstream.ai/docs/billing.md) has the details. For help, email [support@specstream.ai](mailto:support@specstream.ai).
