> For clean Markdown of any page, append `.md` to the page URL.
> Documentation index: https://you.com/docs/llms.txt (section indexes: append `/llms.txt` to any section URL).
> Search these docs: Docs MCP at https://you.com/docs/_mcp/server (`searchDocs`, no API key).
> Call live You.com APIs: Product MCP at https://api.you.com/mcp (keyless `?profile=free` exposes `you-search` and `you-discover`. Research and finance use `/mcp/research`, `/mcp/finance`, or `?tools=` on `/mcp`). To pick an API or integration path, call `you-discover` on that server instead of guessing.
> OpenAPI: https://you.com/docs/openapi.json—auth header `X-API-Key`, env `YDC_API_KEY`.

# Steel

[Steel](https://steel.dev) is an open source browser API that lets you control fleets of browsers in the cloud.

Pair the You.com Web Search API and Contents API with a Steel cloud browser when an agent needs both low-latency web retrieval and real browser actions. The pattern is simple: search first, extract static page content second, and open the browser only when the page requires JavaScript, login state, clicks, filters, or form input.

The Steel cookbook version is available at [docs.steel.dev/cookbook/you-com-search](https://docs.steel.dev/cookbook/you-com-search), with source code in [`examples/you-com-search`](https://github.com/steel-dev/steel-cookbook/tree/e86cfbf8ba715cdbbc49fc2ef13e9fd7798695dc/examples/you-com-search).

## How the Agent Routes Work

The agent gets five tools across two tiers:

| Tier          | Tools                                | Use when                                                                                                                                                                   |
| ------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| You.com APIs  | `youcom_search`, `youcom_contents`   | The answer can be found from search results, static docs, blog posts, news articles, GitHub READMEs, or other pages that return useful HTML or Markdown without a browser. |
| Steel browser | `navigate`, `snapshot`, `click_text` | The page is JavaScript-rendered, login-walled, hidden behind a click, or needs interaction with filters, toggles, or forms.                                                |

The Steel session opens lazily inside the browser tools. If the agent answers with search and contents alone, no browser session is created.

```python
SYSTEM = """
You answer research-style questions by combining You.com APIs with a Steel cloud browser.
Prefer the cheap path first: youcom_search to find candidate URLs, then
youcom_contents to read them. Only call navigate, snapshot, or click_text when
the page is JS-rendered, login-walled, or you need to interact with filters,
toggles, or form fields.
"""

tools = [youcom_search, youcom_contents, navigate, snapshot, click_text]
```

## Create the You.com Tools

Use the Web Search API for discovery and the Contents API to fetch clean Markdown from the URLs you want the model to read. These tools are plain `httpx` calls, so they work with any LangChain agent.

```python
import os
import time
from typing import Any

import httpx
from langchain_core.tools import tool

YOU_BASE = "https://ydc-index.io/v1"
YOUCOM_API_KEY = os.environ["YOUCOM_API_KEY"]


def _headers() -> dict[str, str]:
    return {"X-API-Key": YOUCOM_API_KEY, "Content-Type": "application/json"}


@tool
async def youcom_search(query: str, count: int = 5) -> dict[str, Any]:
    """Search the web for candidate URLs before fetching page content."""
    start = time.perf_counter()
    async with httpx.AsyncClient(timeout=30) as client:
        response = await client.post(
            f"{YOU_BASE}/search",
            json={"query": query, "count": count},
            headers=_headers(),
        )
    response.raise_for_status()
    data = response.json()
    print(f"youcom_search: {(time.perf_counter() - start) * 1000:.0f}ms")
    return data


@tool
async def youcom_contents(urls: list[str]) -> dict[str, Any]:
    """Fetch clean Markdown for up to 10 URLs before escalating to a browser."""
    start = time.perf_counter()
    async with httpx.AsyncClient(timeout=60) as client:
        response = await client.post(
            f"{YOU_BASE}/contents",
            json={"urls": urls, "formats": ["markdown"]},
            headers=_headers(),
        )
    response.raise_for_status()
    data = response.json()
    print(f"youcom_contents: {(time.perf_counter() - start) * 1000:.0f}ms")
    return data
```

## Add Lazy Steel Browser Tools

Keep the Steel session behind `_ensure_session()`. The first browser action creates the session and connects Playwright over Chrome DevTools Protocol (CDP). Later browser tools reuse the same page.

```python
import os
import time

from langchain_core.tools import tool
from playwright.async_api import Page, async_playwright
from steel import Steel

STEEL_API_KEY = os.environ["STEEL_API_KEY"]
steel = Steel(api_key=STEEL_API_KEY)

_session = None
_playwright = None
_browser = None
_page: Page | None = None


async def _ensure_session() -> Page:
    global _session, _playwright, _browser, _page

    if _page is not None:
        return _page

    start = time.perf_counter()
    _session = steel.sessions.create()
    _playwright = await async_playwright().start()
    _browser = await _playwright.chromium.connect_over_cdp(
        f"{_session.websocket_url}&apiKey={STEEL_API_KEY}"
    )
    _page = _browser.contexts[0].pages[0]
    print(f"open_session: {(time.perf_counter() - start) * 1000:.0f}ms")
    return _page


@tool
async def navigate(url: str) -> str:
    """Open a URL in Steel when static extraction is not enough."""
    page = await _ensure_session()
    start = time.perf_counter()
    await page.goto(url, wait_until="domcontentloaded")
    print(f"navigate: {(time.perf_counter() - start) * 1000:.0f}ms")
    return page.url


@tool
async def snapshot() -> str:
    """Read visible text from the live page after JavaScript runs."""
    page = await _ensure_session()
    text = await page.locator("body").inner_text(timeout=10_000)
    print(f"snapshot: {len(text)} chars")
    return text[:20_000]


@tool
async def click_text(text: str) -> str:
    """Click the first visible element whose text matches the input."""
    page = await _ensure_session()
    await page.get_by_text(text).first.click(timeout=10_000)
    await page.wait_for_load_state("domcontentloaded")
    return f"Clicked {text!r}"
```

Release the session in a `finally` block so browser minutes stop even when the agent errors.

```python
async def close_browser() -> None:
    if _browser is not None:
        await _browser.close()
    if _playwright is not None:
        await _playwright.stop()
    if _session is not None:
        steel.sessions.release(_session.id)
```

## Run the LangChain Agent

`create_tool_calling_agent` turns the tool signatures and docstrings into a tool-calling prompt. `AgentExecutor` runs the loop until the model returns a text answer or hits the iteration limit.

```python
import asyncio

from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", SYSTEM),
        ("human", "{input}"),
        MessagesPlaceholder("agent_scratchpad"),
    ]
)

model = ChatOpenAI(model="gpt-5-mini")
agent = create_tool_calling_agent(model, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, max_iterations=10, verbose=False)


async def main() -> None:
    try:
        result = await executor.ainvoke(
            {"input": "Find the latest pricing page for a database company and summarize its free tier."}
        )
        print(result["output"])
    finally:
        await close_browser()


asyncio.run(main())
```

Set `verbose=True` for full LangChain traces. If you use LangSmith, set `LANGSMITH_API_KEY` and `LANGSMITH_TRACING=true` in your environment.

## Run the Steel Cookbook

To run the original cookbook locally:

```bash
steel forge you-com-search
cd examples/you-com-search
cp .env.example .env
# Set STEEL_API_KEY, YOUCOM_API_KEY, and your model provider key.
uv sync
uv run playwright install chromium
uv run main.py
```

Get keys from [Steel](https://app.steel.dev/settings/api-keys), [You.com](https://you.com/platform), and your model provider. New You.com accounts include \$100 in credits with no card required.

A cheap-path run prints only You.com tool timings and ends without opening Steel. An escalated run adds `open_session`, `navigate`, `snapshot`, and a Steel replay URL.

## Production Notes

* Replace the agent with an explicit pipeline when routing must be repeatable: search, fetch contents for the top URLs, then escalate only when the response is too short or contains text like `Please enable JavaScript`.
* Add browser actions such as `fill`, `press`, `wait_for_selector`, and `scroll` when the task needs forms or lazy-loaded content.
* Use the [Research API](/docs/guides/research) instead of `youcom_search` when you want You.com to synthesize a cited answer before the browser tier.
* Keep the browser tier narrow. Steel is the right tool for live interaction, while the Web Search API and Contents API are faster for discovery and static extraction.

## Resources

#### [Steel Cookbook](https://docs.steel.dev/cookbook/you-com-search)

Original Steel guide and runnable starter project.

#### [LangChain Integration](/docs/integrations/langchain)

Use You.com tools and retrievers inside LangChain agents and RAG pipelines.

#### [Web Search API Reference](/docs/api-reference/search/v1-search)

Full Web Search API parameters and response schema.

#### [Contents API Reference](/docs/api-reference/contents)

Full Contents API parameters and response schema.

More copy-and-customize patterns live in [Examples](/docs/examples).