Metadata-Version: 2.4
Name: paperlypdf
Version: 0.7.0
Summary: Python client for Paperly — turn prompts or CSV/Excel files into polished, downloadable PDF reports or PowerPoint slide decks.
License: MIT
Project-URL: Homepage, https://saas-pdf-kappa.vercel.app
Keywords: pdf,pptx,powerpoint,slides,report,ai,generator,document,paperly
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Office/Business
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.25

# Paperly Python SDK

Turn a prompt — or a CSV/Excel file — into a polished, downloadable **PDF report**
or **PowerPoint slide deck** in a few lines of Python. The AI builds a designed,
multi-page document (table of contents, tables, summary section) and renders it
to PDF — no manual formatting.

```bash
pip install paperlypdf
```

## Quickstart

```python
from paperlypdf import PaperlyPdf

client = PaperlyPdf(api_key="pdf_YOUR_API_KEY")

pdf = client.generate_to_pdf(
    "Write a quarterly sales analysis for Q2 2026.",
    length="standard",          # concise | standard | in-depth
    output_path="report.pdf",
)
```

That's it — submit, poll, download. `report.pdf` lands on disk.

## Data file → PDF report (the killer use case)

```python
import csv
import io
from paperlypdf import PaperlyPdf

client = PaperlyPdf(api_key="pdf_YOUR_API_KEY")

def csv_as_text(path):
    with open(path, encoding="utf-8-sig") as f:
        rows = list(csv.DictReader(f))
    out = io.StringIO()
    out.write(f"Source: {path} ({len(rows)} rows)\n\n")
    for row in rows:
        out.write(" | ".join(f"{k}: {v}" for k, v in row.items()))
        out.write("\n")
    return out.getvalue()

client.generate_to_pdf(
    "Organize this data into a clean business report with tables, "
    "then add an analysis section with concrete recommendations.",
    file_text=csv_as_text("sales.csv"),
    length="standard",
    output_path="sales_report.pdf",
)
```

For Excel (`.xlsx`) use pandas to read and pass a text representation the same way:

```python
import pandas as pd
client.generate_to_pdf("Summarize this into a clean report.", file_text=pd.read_excel("orders.xlsx").to_string())
```

## Slide decks (PowerPoint)

The same API also builds **16:9 slide decks**. This is a *generation-time*
choice, not a conversion: the AI is told up front that it is writing slides, so
it writes one idea per slide with real speaker notes — nothing is re-flowed
afterwards, and nothing overflows off the bottom of a slide.

```python
from paperlypdf import PaperlyPdf

client = PaperlyPdf(api_key="pdf_YOUR_API_KEY")

pptx = client.generate_to_pptx(
    "A 12-slide product strategy deck for the Q3 leadership review.",
    length="standard",          # concise 5-7 slides | standard 12-18 | in-depth 28-40
    output_path="strategy.pptx",
)
```

`length` counts **slides** for a deck (5–7 / 12–18 / 28–40). An `in-depth` deck
also gets real PowerPoint speaker notes on every content slide — the depth lives
in the presenter's script instead of being crammed onto the slide.

The `.pptx` reproduces the slide it was rendered from, so it matches the PDF you
would get from the same job: every box is a real, editable PowerPoint shape at
the measured position, carrying the deck's own colours, fonts, corner radii and
images instead of a template's. Layouts survive intact — a row of four stat
cards stays a row of four cards. Text stays text (nothing is rasterised), so the
deck is fully editable in PowerPoint after download.

Want the deck as a PDF instead? Pass `format="deck"` to any PDF call and you get
one 16:9 slide per page:

```python
client.generate_to_pdf(
    "A 12-slide product strategy deck for the Q3 leadership review.",
    length="standard",
    format="deck",
    output_path="strategy.pdf",
)
```

A document can be exported to PowerPoint too (`client.download_pptx(job_id)`),
but it is re-flowed from the A4 text rather than composed as slides. If you want
a deck, ask for one.

## Low-level API

| Method | Purpose |
|---|---|
| `generate(prompt, length, mode, file_text, plain, clarify, answers, web_search, images, format, search_mode)` | Submit a job — returns `jobId` |
| `get_job(job_id)` | Poll status: `processing` / `done` / `error` |
| `wait(job_id)` | Block until `done` (raises on `error` or timeout) |
| `download(job_id, output_path)` | Fetch the finished PDF (bytes) |
| `download_pptx(job_id, output_path)` | Fetch the finished PowerPoint (bytes) |
| `generate_to_pdf(...)` | All of the above in one call |
| `generate_to_pptx(...)` | Same, but always asks for a deck and downloads the `.pptx` |
| `generate_from_text_file(path, prompt, ...)` | Read a text/CSV file locally and generate a PDF from it |
| `generate_from_image_file(path, prompt, ...)` | Read a local image — AI vision analyzes it, then generate a PDF |
| `me()` | API-key info: balance, usage, caps |

### Handling clarifying questions

Pass `clarify=True` and the API may ask questions first:

```python
res = client.generate("I need a document about ice cream.", clarify=True)
if res.get("needsInput"):
    print(res["questions"])   # -> ask the user, then retry with answers:
    res = client.generate("I need a document about ice cream.",
                          clarify=True,
                          answers=["Tutorial", "Beginner home cooks", "3 sections"])
job = client.wait(res["jobId"])
client.download(job["jobId"], "out.pdf")
```

### Live web search (B2B plans)

Pass `web_search=True` to read live web pages and fold their text into the
document, so it can use current facts (prices, news, figures) instead of the
model's memory. Adds a per-job surcharge.

`search_mode` picks how hard to look:

| `search_mode` | What it does | Time | Surcharge |
|---|---|---|---|
| `"deep"` (default) | Searches the live web and reads the pages it finds. Can discover pages the model never learned. | ~20s | higher |
| `"fast"` | Reads pages the model already knows by heart. Cannot discover anything new, and some sites refuse an automated reader. | ~1s | lower |

```python
client.generate_to_pdf(
    "Write a market brief on specialty coffee wholesale pricing in 2026.",
    length="standard",
    web_search=True,          # search_mode defaults to "deep"
    output_path="market-brief.pdf",
)

# Something stable, and you want it now:
client.generate_to_pdf(
    "Summarise the history of the Paris Agreement.",
    web_search=True,
    search_mode="fast",
    output_path="paris.pdf",
)
```

Generation is asynchronous, so the search time shows up in the job's total
duration, not in the call itself.

> **Changed:** with `web_search=True` and no `search_mode`, jobs now run a live
> web search (`"deep"`) where they previously used the quicker recall path.
> Pass `search_mode="fast"` to keep the old behaviour and price.

### Plain document mode

Pass `plain=True` to any PDF generation call for a clean, Word-style document
with minimal visual styling — the PDF reads as hand-written rather than
AI-designed, the file is smaller, and generation is faster and cheaper. Content
stays just as complete. Documents only: a deck is always fully designed, and
`generate_to_pptx` does not take the parameter:

```python
client.generate_to_pdf(
    "A one-page project status memo.",
    length="concise",
    plain=True,
    output_path="memo.pdf",
)
```

### Image → PDF (vision)

Pass one or more images (base64, or a `data:image/...;base64,...` URL). An AI
vision model describes each one and the descriptions fold into the document
context, so photos, sketches, or screenshots become part of the report. Billed
through the file-character surcharge; no vision call runs unless you send images.

```python
client.generate_to_pdf(
    "Describe what this image shows and turn it into a clean document.",
    images=[{"name": "sketch.png", "data": "<base64>"}],
    output_path="from-image.pdf",
)
```

Shortcut — read a local image file directly:

```python
client.generate_from_image_file("photo.png", output_path="from-image.pdf")
```

## Errors

Non-2xx responses raise `PaperlyError` with the server's error message. A job
that fails reaches `status == "error"` and `wait()` raises `PaperlyError` with
the server's reason — a failed generation is never charged. Jobs expire after
30 minutes.

## Pricing

Prepaid, pay-as-you-go: `concise` $0.24 · `standard` $0.24 · `in-depth` $0.80,
plus $0.08 per 100K characters of file data. No subscription.
