Metadata-Version: 2.4
Name: enconvert
Version: 0.0.2
Summary: Enconvert SDK — read any page or file into agent-ready Markdown/JSON/screenshots, every read scored. V2 perception + file conversion.
Author-email: Enconvert <support@enconvert.com>
License-Expression: MIT
Project-URL: Homepage, https://enconvert.com
Project-URL: Repository, https://github.com/conversionapi/python-sdk
Keywords: enconvert,pdf,screenshot,conversion,html-to-pdf,url-to-pdf,url-to-screenshot,url-to-markdown,image-convert,document-convert,heic-to-webp,docx-to-pdf,api,sdk
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
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: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: mypy>=1.8; extra == "dev"
Dynamic: license-file

# enconvert (Python)

Honest eyes for your AI agent — the Python SDK for [Enconvert](https://enconvert.com). Python 3.9+.

Read any web page or file into clean Markdown, JSON, or screenshots, and get a `render_quality` score (0.0–1.0) on **every** read — so a blocked, challenge, or empty-SPA page comes back flagged with a low score and warnings, never mistaken for real content. Perceive, discover, look up, distill, ingest, and watch the web; convert 40+ file and document formats through the same key.

> Wiring an agent (Claude, Cursor, Windsurf, n8n, …)? The [MCP server](https://enconvert.com/mcp) is the native path — `npx @enconvert/mcp setup`. This SDK is the programmatic REST path for everything else.

## Install

```bash
pip install enconvert
```

## Quick Start

```python
from enconvert import Enconvert

client = Enconvert(api_key="sk_...")

# Read a page the way your agent should — with a quality score attached.
op = client.v2.perceive("https://example.com", outputs=["markdown", "structured"])
print(op.outputs["markdown"].url, op.render_quality)  # e.g. 0.93
```

---

# V2 — agent-ready data (`client.v2`)

The V2 namespace turns web pages into agent-ready data: render, search, extract, ingest, and monitor. All V2 endpoints require a **private API key** and are plan-gated — a disabled feature or exhausted monthly quota raises `QuotaError` (HTTP 402).

Every render carries `render_quality` (0.0–1.0). A low score means the page didn't render cleanly (challenge page, cookie wall, empty shell); the content is still returned, flagged, so a bad read never quietly enters your agent's context.

### Perceive — render a URL into artifacts

```python
op = client.v2.perceive(
    "https://example.com",
    outputs=["markdown", "screenshot", "structured"],
    extract=["tables", "metadata"],
)
print(op.render_quality)            # honesty score, 0.0-1.0
print(op.outputs["markdown"].url)   # 15-min signed URL
print(op.structured)

# Re-sign artifact URLs later:
again = client.v2.get_perceive_operation(op.operation_id)

# Batch (<=1000 URLs; small batches run inline, larger return "queued" — poll):
batch = client.v2.perceive_batch(
    ["https://a.com", "https://b.com"],
    outputs=["markdown"],
    output_mode="zip",
)
done = client.v2.get_perceive_batch(batch.job_id)
```

### Discover — enumerate a site's URLs (no rendering)

```python
found = client.v2.discover(
    "https://example.com",
    mode="hybrid",              # "sitemap" | "crawl" | "hybrid"
    max_urls=200,
    exclude_patterns=["/tag/"],
)
print(found.total, found.urls)
```

### Lookup — web search with optional auto-perceive

```python
search = client.v2.lookup(
    "best static site generators",
    category="web",             # web | news | images | scholar | patents | maps
    num_results=10,
    perceive_top=3,              # auto-render top 3 results (uses perceive quota)
)
for hit in search.results:
    print(hit.title, hit.url, hit.perceive.render_quality if hit.perceive else None)
```

### Distill — schema-driven structured extraction

```python
from enconvert import CssField, CssSchema, DistillDiscoverFrom

extraction = client.v2.distill(
    urls=["https://example.com/pricing"],
    schema={"plans": "list of plan names with monthly prices"},
    css_schema=CssSchema(               # optional free CSS pass before the LLM tier
        base_selector=".plan-card",
        fields=[
            CssField(name="name", type="text", selector="h3"),
            CssField(name="price", type="text", selector=".price"),
        ],
    ),
)
print(extraction.results[0].data, extraction.results[0].extraction_tier)

# Or discover-then-distill:
client.v2.distill(
    discover_from=DistillDiscoverFrom(url="https://example.com", mode="sitemap", max_pages=10),
    schema={"title": "page title", "summary": "one-line summary"},
)
```

### Ingest — site or files to RAG-ready JSONL (always async)

Turn a whole site — or a set of uploaded documents — into chunked, RAG-ready JSONL through one pipeline.

```python
from enconvert import IngestChunkOptions

# From a site:
job = client.v2.ingest(
    mode="sitemap",
    url="https://docs.example.com",
    max_pages=100,
    chunk=IngestChunkOptions(max_words=512, sentence_overlap=1),
    webhook_url="https://my.app/hooks/enconvert",
)

# Or from uploaded files (PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT/MD, legacy/ODF office):
file_job = client.v2.ingest_files(
    ["handbook.pdf", "notes.docx"],
    chunk=IngestChunkOptions(max_words=512, sentence_overlap=1),
)

status = client.v2.get_ingest_job(job.job_id)   # poll
if status.status == "completed":
    print(status.output_url)  # JSONL

client.v2.list_ingest_jobs(limit=20)
client.v2.cancel_ingest_job(job.job_id)         # idempotent

# Webhook signing (HMAC):
secret = client.v2.get_webhook_secret()
print(secret.secret, secret.signature_header)
client.v2.rotate_webhook_secret()               # invalidates old secret
client.v2.retry_ingest_webhook(job.job_id)      # re-deliver
```

### Watch — recurring change monitoring

```python
watcher = client.v2.create_watcher(
    "https://example.com/pricing",
    frequency_minutes=60,        # hourly floor
    diff_mode="auto",            # auto | text | structured | tables | metadata
    webhook_url="https://my.app/hooks/changes",
    notify_email=True,
)

client.v2.list_watchers()
client.v2.get_watcher(watcher.watcher_id)
client.v2.get_watcher_snapshots(watcher.watcher_id, limit=10)
client.v2.update_watcher(watcher.watcher_id, status="paused")
client.v2.update_watcher(watcher.watcher_id, webhook_url="")  # clears webhook
client.v2.delete_watcher(watcher.watcher_id)  # soft-delete, idempotent
```

### V2 error handling

```python
from enconvert import QuotaError

try:
    client.v2.ingest(mode="sitemap", url="https://example.com")
except QuotaError:
    print("Upgrade plan or wait for quota reset")
```

---

# File conversion

The same key also converts 40+ formats. Two "anything → X" endpoints auto-detect the input; the format-specific methods below give you a validated, typed path.

### Anything to Markdown / PDF

```python
# Any document -> clean Markdown (a RAG-ingestion building block):
client.convert_to_markdown("report.docx", save_to="report.md")
# PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT/MD, and legacy/ODF office. (Images not supported.)

# Almost anything -> PDF:
client.convert_to_pdf("slides.pptx", save_to="slides.pdf")
# office/ODF/Pages/Numbers/RTF/CSV, HTML, Markdown, text, images, SVG, EPUB, or a PDF passthrough.

# Only pdf_options.grayscale is honored on this endpoint:
from enconvert import PdfOptions

client.convert_to_pdf("scan.pdf", pdf_options=PdfOptions(grayscale=True), save_to="gray.pdf")
```

### Image Conversion

```python
result = client.convert_image(
    "photo.heic",
    output_format="webp",
    save_to="photo.webp",
)
```

Any pair among `jpeg`, `png`, `svg`, `heic`, `webp` — plus PDF rasterization:

```python
client.convert_image("scan.pdf", output_format="jpeg", save_to="scan.jpeg")
```

### Document Conversion

```python
client.convert_document("report.docx", save_to="report.pdf")
client.convert_document("data.json", output_format="yaml", save_to="data.yaml")
client.convert_document("notes.md", output_format="html", save_to="notes.html")
```

Supported inputs: `doc`/`docx`, `xls`/`xlsx`, `ppt`/`pptx`, `odt`, `ods`, `odp`, `ots`, `pages`, `numbers`, `html`, `markdown`, `csv`, `json`, `xml`, `yaml`, `toml`. (EPUB → use `convert_to_pdf` / `convert_to_markdown`.)

The SDK validates every `{input}-to-{output}` pair against the conversions the API actually implements and raises immediately — with the list of valid outputs for that input — instead of sending a doomed request. Introspect programmatically:

```python
from enconvert import IMPLEMENTED_CONVERSIONS, valid_outputs_for

valid_outputs_for("json")  # ["csv", "toml", "xml", "yaml"]
valid_outputs_for("pdf")   # ["jpeg"]
```

### Supported conversions

| Input | Outputs |
|-------|---------|
| json | csv, toml, xml, yaml |
| xml | csv, json |
| yaml | json |
| csv | json, xml |
| toml | json |
| markdown | html, pdf |
| html | pdf |
| doc, excel, ppt, odt, ods, odp, ots, pages, numbers | pdf |
| jpeg, png, svg, heic, webp | each other (all 20 pairs) |
| pdf | jpeg |

### URL to PDF / Screenshot / Markdown

```python
client.convert_url_to_pdf("https://example.com", save_to="page.pdf")
client.convert_url_to_screenshot("https://example.com", viewport_width=1440, save_to="shot.png")
client.convert_url_to_markdown("https://example.com/article", save_to="article.md")
```

Extract clean GitHub-Flavored Markdown from any URL — strips nav/footer/ads/scripts, keeps the main article content, and adds YAML frontmatter (title, description, url, links, images).

### Website to PDF / Screenshot (whole-site batch)

Discover every page of a website (via sitemap, or full crawl on higher plans), convert each one in the background, and receive a single ZIP. Requires a private API key with crawl access.

```python
batch = client.convert_website_to_pdf(
    "https://example.com",
    crawl_mode="sitemap",             # "auto" (default) | "sitemap" | "full"
    exclude_patterns=["/blog/tag/"],  # full crawl mode only
)
print(batch.batch_id, batch.url_count, batch.discovery_method)

# Block until done and save the ZIP:
status = client.wait_for_batch(batch.batch_id, save_to="site.zip")
print(status.completed, "of", status.total, "pages converted")

# Or poll yourself:
s = client.get_batch_status(batch.batch_id)
if s.status != "processing":
    print(s.zip_download_url)
```

`convert_website_to_screenshot` works the same way and produces a ZIP of PNGs.

### PDF options & authenticated pages

```python
from enconvert import BrowserCookie, HttpBasicAuth, PdfHeaderFooter, PdfMargins, PdfOptions

client.convert_url_to_pdf(
    "https://internal.example.com/report",
    pdf_options=PdfOptions(
        page_size="A4",              # or custom dimensions via page_width + page_height
        orientation="landscape",
        margins=PdfMargins(top=10, bottom=10, left=15, right=15),
        header=PdfHeaderFooter(content="Quarterly Report", height=15),
        footer=PdfHeaderFooter(content="Confidential", height=12),
    ),
    auth=HttpBasicAuth(username="user", password="pass"),     # or cookies / headers, plan-gated
    cookies=[BrowserCookie(name="session", value="abc123", domain="internal.example.com")],
    headers={"X-Tenant": "acme"},
    save_to="report.pdf",
)
```

Do not combine `auth` with an `Authorization` header — the API rejects the conflict.

### Job status (async polling)

```python
status = client.get_job_status("job_abc123")
if status.status == "success":
    print(status.presigned_url)
```

---

## Error Handling

```python
from enconvert import Enconvert, APIError, AuthenticationError, RateLimitError, QuotaError

try:
    client.v2.perceive("https://example.com")
except AuthenticationError:
    print("Invalid API key")
except QuotaError:
    print("Plan feature off or quota exhausted")
except RateLimitError:
    print("Too many requests — slow down")
except APIError as e:
    print(f"API error [{e.status_code}]: {e.message}")
```

## Configuration

```python
client = Enconvert(
    api_key="sk_...",
    timeout=300.0,  # seconds, default
    base_url="https://api.enconvert.com",  # default
)
```

## Get an API Key

Sign up at [enconvert.com](https://enconvert.com). Free tier: 100 ops/month, no credit card.

## License

MIT
