Metadata-Version: 2.4
Name: fetchora
Version: 0.1.0
Summary: Official Python client for the Scraping SaaS REST API
Project-URL: Homepage, https://github.com/Nodirbek2001/scraper
Project-URL: Issues, https://github.com/Nodirbek2001/scraper/issues
Author-email: Scraping Team <support@scraping.io>
License: MIT
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<1.0,>=0.27
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Description-Content-Type: text/markdown

# fetchora — Python SDK

Official Python client for the Scraping SaaS REST API. Sync + async interfaces,
zero-setup auth (`SCRAPING_API_KEY` env var), wait-with-fallback semantics.

## Install

```bash
pip install fetchora
```

Requires Python 3.9+.

## Quickstart

```python
from fetchora import ScrapingClient

client = ScrapingClient()  # SCRAPING_API_KEY read from env
page = client.scrape("https://example.com")
print(page["result"]["markdown"])
```

```python
import asyncio
from fetchora import AsyncScrapingClient

async def main():
    async with AsyncScrapingClient() as client:
        page = await client.scrape("https://example.com")
        print(page["result"]["markdown"])

asyncio.run(main())
```

## Environment variables

| Variable | Required | Default | Description |
|---|---|---|---|
| `SCRAPING_API_KEY` | yes (unless passed explicitly) | — | Bearer token |
| `SCRAPING_BASE_URL` | no | `https://api.scraping.io` | Override for self-hosted / dev |

## API reference

| Method | REST |
|---|---|
| `get_balance()` | `GET /v1/billing/balance` |
| `scrape(url, *, wait=True, wait_timeout_s=60, cookies=None, format="markdown")` | `POST /v1/scrape` + poll |
| `get_scrape_status(task_id)` | `GET /v1/scrape/{id}` |
| `crawl(urls, *, wait=True, wait_timeout_s=600, cookies=None, include_items=True)` | `POST /v1/crawl` + poll |
| `get_crawl_status(crawl_id, *, include_items=True)` | `GET /v1/crawl/{id}` + fan-out |

On `wait=True` timeout → `ScrapingTimeoutError`. On `wait=False` → returns `{"task_id": ...}` / `{"crawl_id": ...}` immediately.

## Errors

All errors inherit from `fetchora.ScrapingError`:

| Class | Code | Trigger |
|---|---|---|
| `InvalidApiKeyError` | `invalid_api_key` | 401 |
| `InsufficientBalanceError` | `insufficient_balance` | 402 |
| `ForbiddenError` | `forbidden` | 403 |
| `NotFoundError` | `not_found` | 404 |
| `AlreadyTerminalError` | `already_terminal` | 409 |
| `InvalidInputError` | `invalid_input` | 400, 422 |
| `ServerError` | `server_error` | 5xx |
| `ScrapingTimeoutError` | `timeout` | SDK wait-timeout |

## License

MIT. See [repo root](https://github.com/Nodirbek2001/scraper) for source.
