> ## Documentation Index
> Fetch the complete documentation index at: https://outcrawl.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> Official Python SDK for Outcrawl. Pure Python, one wheel for every OS, async and context managed.

export const OutcrawlStyles = () => {
  if (typeof document !== 'undefined' && !document.getElementById('outcrawl-overrides')) {
    const s = document.createElement('style');
    s.id = 'outcrawl-overrides';
    s.textContent = `
      /* Match the product's radius grid (4 / 6 / 8 / 9999), not Mintlify's default. */
      .rounded-sm { border-radius: 0.375rem !important; } /* 6px, --r-ctl */
      .rounded-md { border-radius: 0.375rem !important; } /* 6px, --r-ctl */
      .rounded-lg { border-radius: 0.5rem !important; }   /* 8px, --r-panel */
      .rounded-l-sm { border-top-left-radius: 0.375rem !important; border-bottom-left-radius: 0.375rem !important; }
      .rounded-r-sm { border-top-right-radius: 0.375rem !important; border-bottom-right-radius: 0.375rem !important; }
      .rounded-l-md { border-top-left-radius: 0.375rem !important; border-bottom-left-radius: 0.375rem !important; }
      .rounded-r-md { border-top-right-radius: 0.375rem !important; border-bottom-right-radius: 0.375rem !important; }
      .rounded-l-lg { border-top-left-radius: 0.5rem !important; border-bottom-left-radius: 0.5rem !important; }
      .rounded-r-lg { border-top-right-radius: 0.5rem !important; border-bottom-right-radius: 0.5rem !important; }

      /* Cards: never pure white in light mode, matching the one-big-card surface tone. */
      .card { background-color: #fcfcfc !important; border-radius: 0.5rem !important; }
      html.dark .card { background-color: #141311 !important; }

      /* Docs hero box */
      .oc-hero { background-color: #fcfcfc; border: 1px solid #e5e2dc; }
      html.dark .oc-hero { background-color: #141311; border-color: #26241f; }
      html.dark .oc-hero h1 { color: #edeae4; }

      /* Thin, transparent-track, hide-until-hover scrollbar. */
      ::-webkit-scrollbar { width: 6px; height: 6px; background-color: transparent; }
      ::-webkit-scrollbar-track { background-color: transparent; }
      ::-webkit-scrollbar-thumb { background-color: rgba(155, 155, 155, 0.5); border-radius: 10px; transition: opacity 0.3s ease; opacity: 0; }
      ::-webkit-scrollbar-thumb:hover { background-color: rgba(155, 155, 155, 0.7); }
      *:hover::-webkit-scrollbar-thumb,
      *:focus::-webkit-scrollbar-thumb,
      *:active::-webkit-scrollbar-thumb { opacity: 1; }
      * { scrollbar-width: thin; scrollbar-color: rgba(155, 155, 155, 0.5) transparent; }
    `;
    document.head.appendChild(s);
  }
  return null;
};

<OutcrawlStyles />

The official Python client. Pure Python: one `py3-none-any` wheel for every operating system, no compiler required on any of them, one runtime dependency (`httpx`, itself pure Python).

## Installation

```bash theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
pip install outcrawl
```

Requires Python 3.10 or newer.

## Quick start

```python theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
import asyncio
from outcrawl import Outcrawl

async def main() -> None:
    async with Outcrawl() as oc:                       # reads OUTCRAWL_API_KEY
        doc = await oc.scrape("https://example.com", formats=["markdown"])
        print(doc.markdown)                             # or doc["markdown"], it is the wire object
        print(doc.usage)                                # every call returns what it cost

        job = oc.agent(
            task="Report the title of the top story on Hacker News.",
            caps={"budget": "16.00"},                    # the one ceiling a submit must carry
        )
        async for record in job:                        # the event cursor, while it runs
            print(record["kind"], record["value"])
        run = await job                                 # and the settled run, on the same job
        print(run["status"], run.get("data"))           # data is absent until there is an answer

asyncio.run(main())
```

Async and context managed throughout, a genuinely different shape from the TypeScript client. `await` and `async for` on the same job object are two separate requests against one durable row, watching a run and having its answer is not a choice between them.

## Configuration

| Variable | Meaning | Default |
| - | - | - |
| `OUTCRAWL_API_KEY` | Your key | Required |
| `OUTCRAWL_API_URL` | Base URL | `https://api.outcrawl.ai/v1` |

The same two names the TypeScript SDK and the CLI read. Neither SDK uses the CLI's stored `outcrawl login` session: a script needs `OUTCRAWL_API_KEY` or a key passed in. Pass `Outcrawl(api_key=..., base_url=...)` to override per client.

## Surface

Every one of the 46 capabilities is reachable except live browser control.

* `oc.scrape(url, **options)` / `oc.crawl(url, **options)` / `oc.search(query, **options)`. `await oc.crawl(url)` gives you a crawl you read with `async for`, one page at a time as each is fetched.
* `oc.crawl.job(id)` reaches a crawl already in progress: `status()`, `results()`, `cancel()`. Useful when a connection drops, hanging up on a streaming crawl cancels it, but the pages already delivered stay yours; these routes are how you read what was already paid for.
* `oc.agent(task=..., caps=...)`. `caps` is required at runtime and `caps.budget` inside it; `caps.steps` and `caps.duration` are optional. The job is awaitable, async-iterable, and carries `status()`, `results()`, `events()`, `cancel()`, `control()`, `answer()`, `add_file()`. `schema` accepts a JSON Schema object and constrains `run["data"]` to it, the same typed submit the TypeScript SDK documents.
* `oc.profiles`, `oc.secrets`, `oc.rules`, `oc.integrations`, `oc.sessions`, `oc.monitors`.
* `oc.usage(**query)` and `oc.credits()`. Every credit figure is a decimal string, use `decimal.Decimal`, never `float()`.

## What is not here

`oc.browser()`. Live browser control needs a CDP connection held open in the caller's own process, which is why it exists only in the TypeScript SDK. The same underlying page capability is reachable through `agent` and `scrape`, which run the loop on the platform's own side, a difference in kind rather than an omission. `Session.take_control()` is absent for the identical reason, it opens a CDP connection to a running session's own page so a person can drive it directly.

## Errors

The same classes, same fields, same messages as the TypeScript SDK, kept in parity by a shared test fixture so neither client can drift from the other silently.

```python theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
import asyncio
from outcrawl import Outcrawl, ProfileInUseError, QuotaExceededError

async def main() -> None:
    async with Outcrawl() as oc:
        profile = await oc.profiles.create(label="acme-user-42")
        try:
            doc = await oc.scrape("https://example.com", profile=profile.id)
            print(doc.markdown)
        except ProfileInUseError as leased:
            print("profile busy until", leased.held_until)
        except QuotaExceededError as over:
            await asyncio.sleep(over.retry_after_seconds)

asyncio.run(main())
```

Branch on `error.code`, never on `str(error)`. See [errors](/docs/api/errors) for the full table.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.