# isitcredible — Python SDK

Python client for the [isitcredible.com](https://isitcredible.com) automated peer-review API.

## Installation

```bash
pip install isitcredible
```

## Quick start

```python
from isitcredible import Client

client = Client("iic_your_api_key")
report = client.analyze("paper.pdf")
report.save("review.pdf")
```

`analyze()` submits the document and polls until the report is ready, then returns a `Report` object. **Jobs take at least 20 minutes and may run longer** — the default timeout is 60 minutes.

## API key

Generate a key from your [account page](https://isitcredible.com/account). Keys start with `iic_` and require a verified email address.

```python
client = Client("iic_your_api_key")
```

You can also read the key from an environment variable:

```python
import os
from isitcredible import Client

client = Client(os.environ["IIC_API_KEY"])
```

## Report types

| Mode | Price | Description |
|------|-------|-------------|
| `standard` | $5.00 | Full peer review: methodological critique, identified issues, directions for future research. |
| `extended` | $7.00 | Everything in *standard*, plus an editorial response note and a proofreading pass. |

```python
report = client.analyze("paper.pdf", mode="extended")
```

## Saving the report

```python
report.save("review.pdf")          # Save to a specific path
report.save(format="txt")          # Save as TXT (defaults to {job_id}.txt)
report.save()                      # Save as PDF (defaults to {job_id}.pdf)
```

Or get the raw bytes:

```python
pdf_bytes = report.download()
```

## Handling timeouts

Because jobs are long-running, `analyze()` (and `wait()`) will raise `JobTimeoutError` if the job doesn't finish within the timeout. The job **keeps running on the server** — you can resume polling using the `job_id` attached to the exception:

```python
from isitcredible import Client, JobTimeoutError

client = Client("iic_your_api_key")

try:
    report = client.analyze("paper.pdf", timeout=3600)  # 60 min
except JobTimeoutError as e:
    print(f"Timed out. Job is still running: {e.job_id}")
    # Resume later:
    report = client.wait(e.job_id, timeout=3600)
    report.save("review.pdf")
```

If you lose track of a job ID, use `list_jobs()` to find it:

```python
jobs = client.list_jobs()
# Each entry has 'uuid', 'status', 'title', 'mode', 'audit_date'
```

## Advanced: submit and poll separately

Use `submit()` and `wait()` directly if you need to fire-and-forget or manage polling yourself:

```python
# Submit without blocking
job = client.submit("paper.pdf", mode="standard")
job_id = job["job_id"]
print(f"Submitted: {job_id}")

# ... do other things ...

# Resume polling later
report = client.wait(job_id, poll_interval=60, timeout=7200)
report.save("review.pdf")
```

## Checking your balance

```python
balance = client.get_credits()
print(f"${balance['balance_usd']:.2f} remaining")
```

## Webhooks

Register a webhook to receive a POST when a job completes, instead of polling:

```python
# Register (secret is shown once — save it)
hook = client.create_webhook("https://yourapp.com/webhook")
print(hook["secret"])

# Or pass a webhook URL per job
client.submit("paper.pdf", webhook_url="https://yourapp.com/webhook")

# Manage webhooks
hooks = client.list_webhooks()
client.delete_webhook(hooks[0]["id"])
```

## Full method reference

| Method | Description |
|--------|-------------|
| `analyze(file_path, mode, note, webhook_url, poll_interval, timeout)` | Submit + poll + return `Report`. Blocks until complete. |
| `submit(file_path, mode, note, webhook_url)` | Submit without waiting. Returns `dict` with `job_id`. |
| `wait(job_id, poll_interval, timeout)` | Poll an existing job to completion. Returns `Report`. |
| `get_job(job_id)` | Get current status and metadata for a job. |
| `list_jobs()` | List your last 50 jobs. |
| `get_credits()` | Get your current credit balance. |
| `create_webhook(url, event)` | Register a webhook. Returns signing secret (shown once). |
| `list_webhooks()` | List registered webhooks. |
| `delete_webhook(id)` | Delete a webhook. |

### Report object

| Attribute | Description |
|-----------|-------------|
| `job_id` | Job UUID. |
| `status` | `completed` or `published`. |
| `mode` | `standard` or `extended`. |
| `title` | Paper title (extracted from document). |
| `authors` | Authors (extracted from document). |
| `discipline` | Academic discipline (extracted from document). |
| `report_url` | Direct URL to download the report. |
| `.download(format)` | Return report as bytes (`"pdf"` or `"txt"`). |
| `.save(path, format)` | Write report to a file. |

## Errors

| Exception | Description |
|-----------|-------------|
| `IsItCredibleError` | Base class. Has `.status_code` and `.detail`. |
| `JobTimeoutError` | Polling timed out. Has `.job_id` — use it to resume with `wait()`. |

HTTP errors map to `IsItCredibleError` with the relevant status code:

| Code | Meaning |
|------|---------|
| `401` | Missing or invalid API key. |
| `402` | Insufficient balance. |
| `403` | Email not verified. |
| `404` | Job not found. |
| `400` | Invalid input (file too large, wrong format, etc.). |
