# fetcha — Python SDK Official Python client for [Fetcha](https://www.fetcha.co), the intelligent web-access API. Standard library only, Python 3.9+. ```bash pip install fetcha ``` ```python from fetcha import Fetcha client = Fetcha(api_key="fch_live_...") # Fetch a page — Fetcha picks the network, geography and retries for you result = client.fetch("https://example.com", country="CA") print(result.status, result.metadata["network"], result.metadata["mode"], result.metadata["attempts"]) print(result.content[:500]) # Readable text, Markdown or JSON helpers text = client.text("https://example.com") md = client.markdown("https://example.com/article") # main content as Markdown data = client.json("https://api.example.com/items") # Page metadata and links come with every HTML response r = client.fetch("https://example.com", links=True) print(r.page["title"], r.page["links_count"], r.links[:3]) # Managed browser rendering (same network / country / session as a plain fetch) r = client.render("https://app.example.com/dashboard", wait_for="table.results", screenshot=True) open("dashboard.png", "wb").write(__import__("base64").b64decode(r.screenshot)) # Blocked HTTP attempts escalate to the browser automatically (browser_fallback=True by default); # r.mode tells you whether the final attempt was "http" or "browser". # Sticky sessions keep the same exit identity across requests session = client.sessions.create(country="CA", ttl=600) page1 = client.fetch("https://example.com/login", session=session.id) page2 = client.fetch("https://example.com/account", session=session.id) # Crawl a site into Markdown (asynchronous job) job = client.crawl.create(url="https://docs.example.com/", max_pages=200, max_depth=3, include_patterns=["/docs/*"]) job = client.crawl.wait(job.id, poll_s=2.0, timeout_s=600) # polls until completed | failed | cancelled print(job.status, job.stats) # {'discovered': …, 'fetched': …, 'ok': …, 'blocked': …, 'failed': …, 'bytes': …} for page in client.crawl.iter_pages(job.id, status="success"): print(page.url, page.title, len(page.content or "")) # or page manually: client.crawl.pages(job.id, cursor=None, limit=100).next_cursor client.crawl.cancel(job.id) # Map a site's URLs (sitemap + links) without fetching every page m = client.map("https://docs.example.com/", search="/docs/*", limit=500) print(m.count, m.urls[:5], m.sources, m.truncated) ``` Errors raise `fetcha.FetchaError` with `.code`, `.status`, `.request_id` and `.details`.