Metadata-Version: 2.4
Name: markproof
Version: 0.2.0
Summary: Python client for the Markproof API - EU AI Act Article 50 watermarking and verification.
Author-email: Markproof <support@markproof.co>
License-Expression: MIT
Project-URL: Homepage, https://markproof.co
Project-URL: Documentation, https://markproof.co/docs
Keywords: eu-ai-act,article-50,watermark,c2pa,content-credentials,ai-generated,provenance
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Multimedia :: Sound/Audio
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.31
Dynamic: license-file

# markproof

Python client for the [Markproof](https://markproof.co) API — EU AI Act Article 50 watermarking and verification in one call. Thin wrapper over `requests`.

> **Is your AI output Article 50-ready?** Drop a file into the [free checker](https://markproof.co/check) (no signup) to see whether it carries a watermark and signed Content Credentials.

## Install

```bash
pip install markproof
```

## Usage

```python
from markproof import Markproof

client = Markproof(api_key="lt_live_...")

marked = client.mark("generated.png", generator_name="my-app", model_name="my-model-v1")
print(marked.watermark_code, marked.verify_url)
with open("generated-marked.png", "wb") as f:
    f.write(marked.file)

result = client.verify("generated-marked.png")
print(result.summary)
```

## API

- `Markproof(api_key, base_url=DEFAULT_BASE_URL)` — `base_url` defaults to the production API; override for local dev.
- `client.mark(file, filename=None, generator_name=None, model_name=None, response_format="file", visible_label=None, label_position=None)` → `MarkResult` — `file` is a path (`str`/`os.PathLike`) or raw `bytes` (in which case `filename` is required). `response_format="file"` (default) returns the marked bytes inline (`result.file`/`result.content_type`); `"url"` returns a short-lived signed download URL instead (`result.file_url`, ~1 hour — download promptly rather than caching it). Either way you also get `asset_id`, `watermark_code`, `verify_url`, `c2pa_manifest_label`. `visible_label` (images only): `True` draws a visible "AI-generated" label (EU AI Act Art. 50(4)) before marking, or pass your own text (max 40 characters); `label_position` picks the corner (`"bottom-right"` default, `"bottom-left"`, `"top-right"`, `"top-left"`).
- `client.verify(file, filename=None)` → `VerifyResult` — `watermark`/`c2pa` detection results, `matched_asset` if the watermark matches a record in Markproof's database, and a human-readable `summary`.
- `client.get_asset(asset_id)` → `Asset` — requires an API key on the same project that created the asset.

All methods raise `MarkproofError` (with `.status` and `.body`) on a non-2xx response.

## Batches and webhooks

For volume, queue many files at once instead of calling `mark` per file. Each item counts as one mark.

```python
created = client.create_batch(
    urls=["https://cdn.example.com/1.png", "https://cdn.example.com/2.png"],  # and/or files=["a.png", (data, "b.wav")]
    generator_name="your-app",
    webhook_url="https://your-app.example.com/hooks/markproof",  # optional
)
batch = client.wait_for_batch(created.batch_id)  # or get_batch(...), or wait for the webhook
for item in batch.items:
    print(item.index, item.status, item.file_url or item.error)
```

- `client.create_batch(files=None, urls=None, generator_name=None, model_name=None, webhook_url=None)` → `CreatedBatch` — files go up in one multipart request (keep the total under ~4.5 MB; use `urls` for more). Max items per batch: 10 Free, 100 Indie, 1,000 Pro.
- `client.get_batch(batch_id)` → `Batch` — `status` (`queued`/`processing`/`completed`/`partial`/`failed`), `counts`, and `items` with `asset_id`, `watermark_code`, `verify_url`, `file_url` (signed, ~1 hour; call again for a fresh one) or `error`.
- `client.wait_for_batch(batch_id, interval=3.0, timeout=900, on_progress=None)` — polls until the batch is final.

Verify webhooks with the raw request body and your signing secret from the dashboard:

```python
from markproof import verify_webhook_signature

ok = verify_webhook_signature(
    request.get_data(),  # the exact bytes received, not re-serialized JSON
    request.headers.get("Markproof-Signature"),
    os.environ["MARKPROOF_WEBHOOK_SECRET"],
)
```

`parse_webhook_event(...)` does the same and returns the `batch.completed` event, raising `WebhookSignatureError` if the signature doesn't match. Event `id`s are stable across retries, so use them to drop duplicates.
