Metadata-Version: 2.4
Name: pureframe-ai
Version: 1.0.0
Summary: Python client for the Pure Frame API — search any moment inside your videos with natural language
Project-URL: Homepage, https://pureframe.ai
Project-URL: Documentation, https://docs.pureframe.ai
Project-URL: Repository, https://github.com/pureframeai/pureframe-python
Project-URL: Issues, https://github.com/pureframeai/pureframe-python/issues
License: MIT
License-File: LICENSE
Keywords: multimodal,pureframe,search,video,video-intelligence
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.21.2
Requires-Dist: pydantic>=1.9.2
Requires-Dist: python-dateutil>=2.9.0
Requires-Dist: typing-extensions>=4.0.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23.5; extra == 'dev'
Requires-Dist: pytest>=7.4.0; extra == 'dev'
Description-Content-Type: text/markdown

# Pureframe Python API library

<!-- prettier-ignore -->
[![PyPI version](https://img.shields.io/pypi/v/pureframe-ai.svg?label=pypi%20(stable))](https://pypi.org/project/pureframe-ai/)

Pureframe Python library provides convenient access to the [Pureframe](https://pureframe.ai) REST API from any Python 3.9+
application. The library includes type definitions for all request params and response fields,
and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).

It is generated from our OpenAPI specification with [Fern](https://buildwithfern.com). The import name is `pureframe`; the PyPI distribution is `pureframe-ai`.

## Documentation

The REST API documentation can be found on [docs.pureframe.ai](https://docs.pureframe.ai).

## Installation

```sh
# install from PyPI
pip install pureframe-ai
```

## Usage

The primary workflow is: create a collection, upload videos to it, then search across indexed video segments with natural language, an image, or both.

```python
from pureframe import Pureframe

client = Pureframe(
    token="pf_...",
)

collection = client.collections.create_collection(name="Product demos")

results = client.search.search(
    query="pricing objection",
    collection_id=collection.data.id,
)

for result in results.data:
    print(result.filename, [segment.timestamp_start for segment in result.segments])
```

While you can provide a `token` keyword argument,
we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
to store your API key in a `.env` file and reading it with `os.environ.get(...)`
so that your API key is not stored in source control.

```python
import os
from pureframe import Pureframe

client = Pureframe(
    token=os.environ.get("PUREFRAME_API_KEY"),
)
```

### Uploading videos

Upload a video to a collection for indexing. Processing happens asynchronously — use the returned `job_id` to poll the job until it completes.

```python
from pathlib import Path
from pureframe import Pureframe

client = Pureframe(token="pf_...")

upload = client.upload.upload_video(
    collection_id=collection.data.id,
    file=Path("input.mp4"),
)

job = client.jobs.get_job(upload.data.job_id)
print(job.data.status)  # "queued" -> "processing" -> "done" (or "failed")
```

Once the job status is `done`, the video is fully searchable.

### Search

At least one of `query`, `image`, or `image_url` is required. Results are grouped by video and include timestamped segments.

```python
from pureframe import Pureframe

client = Pureframe(token="pf_...")

# Text search
results = client.search.search(query="pricing objection")

# Image search, by file
with open("path/to/frame.png", "rb") as image_file:
    results = client.search.search(image=image_file)

# Image search, by URL
results = client.search.search(image_url="https://example.com/frame.png")
```

You can also submit feedback on a search result to help improve ranking:

```python
client.search.submit_search_feedback(
    video_id=results.data[0].video_id,
    timestamp_start=results.data[0].segments[0].timestamp_start,
    signal="positive",
    query="pricing objection",
)
```

## Async usage

Simply import `AsyncPureframe` instead of `Pureframe` and use `await` with each API call:

```python
import os
import asyncio
from pureframe import AsyncPureframe

client = AsyncPureframe(
    token=os.environ.get("PUREFRAME_API_KEY"),
)

async def main() -> None:
    results = await client.search.search(query="pricing objection")
    print(results.data)

asyncio.run(main())
```

Functionality between the synchronous and asynchronous clients is otherwise identical.

## Using types

Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like:

- Serializing back into JSON, `model.model_dump_json()` (or `model.json()` on Pydantic v1)
- Converting to a dictionary, `model.model_dump()` (or `model.dict()` on Pydantic v1)

Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`.

## Pagination

List methods in the Pureframe API are paginated via `page` and `per_page` parameters. Each list response includes a `meta` object with pagination details (`total`, `page`, `per_page`).

```python
from pureframe import Pureframe

client = Pureframe(token="pf_...")

all_videos = []
page = 1
while True:
    response = client.videos.list_videos(
        collection_id=collection.data.id,
        page=page,
        per_page=50,
    )
    all_videos.extend(response.data)
    if len(all_videos) >= (response.meta.total or 0):
        break
    page += 1

print(all_videos)
```

Or, asynchronously:

```python
import asyncio
from pureframe import AsyncPureframe

client = AsyncPureframe(token="pf_...")

async def main() -> None:
    all_videos = []
    page = 1
    while True:
        response = await client.videos.list_videos(
            collection_id="collection_id",
            page=page,
            per_page=50,
        )
        all_videos.extend(response.data)
        if len(all_videos) >= (response.meta.total or 0):
            break
        page += 1
    print(all_videos)

asyncio.run(main())
```

## File uploads

Request parameters that correspond to file uploads (video uploads, image search) can be passed as `bytes`, a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, a file-like object, or a tuple of `(filename, contents, media type)`.

```python
from pathlib import Path
from pureframe import Pureframe

client = Pureframe(token="pf_...")

client.upload.upload_video(
    collection_id="collection_id",
    file=Path("input.mp4"),
)
```

The async client uses the exact same interface.

## Webhooks

Register a webhook endpoint to receive notifications about processing job events instead of polling.

```python
from pureframe import Pureframe

client = Pureframe(token="pf_...")

endpoint = client.webhooks.create_endpoint(
    url="https://example.com/webhook",
    events=["job.completed", "job.failed"],
)

# Update, list, or delete endpoints
client.webhooks.update_endpoint("we_abc123", is_active=False)
client.webhooks.list_endpoints()
client.webhooks.delete_endpoint("we_abc123")
```

You can also pass a `webhook_url` directly when uploading a video, to be notified once that specific job finishes:

```python
client.upload.upload_video(
    collection_id="collection_id",
    file=Path("input.mp4"),
    webhook_url="https://example.com/webhook",
)
```

## AI agent tool-calling

The `agent` client exposes an OpenAI-compatible tool schema so LLM agents can call Pureframe tools directly.

```python
from pureframe import Pureframe

client = Pureframe(token="pf_...")

schema = client.agent.agent_schema()

result = client.agent.agent_call(
    tool="search_videos",
    input={"query": "pricing objection"},
)
```

`search_videos` results include `thumbnail_base64` — a base64-encoded JPEG that vision-capable models (GPT-4o, Claude) can consume directly.

## Handling errors

When the API returns a non-success status code (4xx or 5xx response), a `pureframe.core.api_error.ApiError` is raised, containing `status_code` and `body` properties. For `422` validation errors, an `UnprocessableEntityError` (a subclass of `ApiError`) is raised with a structured `body`.

```python
from pureframe import Pureframe
from pureframe.core.api_error import ApiError
from pureframe.errors import UnprocessableEntityError

client = Pureframe(token="pf_...")

try:
    client.collections.create_collection(name="Product demos")
except UnprocessableEntityError as e:
    print("Validation error:", e.body)
except ApiError as e:
    print(e.status_code)
    print(e.body)
```

## Retries

Certain requests can be retried by passing `max_retries` via `request_options`. Retries use a short exponential backoff. This is off by default (`max_retries=0`) and is configured per-request rather than on the client:

```python
from pureframe import Pureframe

client = Pureframe(token="pf_...")

client.videos.list_videos(
    collection_id="collection_id",
    request_options={"max_retries": 3},
)
```

## Timeouts

By default requests time out after 60 seconds. You can configure this globally on the client, or per-request via `request_options`:

```python
from pureframe import Pureframe

# Configure the default for all requests:
client = Pureframe(
    token="pf_...",
    timeout=20.0,
)

# Override per-request:
client.videos.list_videos(
    collection_id="collection_id",
    request_options={"timeout_in_seconds": 5},
)
```

## Advanced

### Undocumented request params

If you want to explicitly send an extra param, you can do so via the `additional_query_parameters`, `additional_body_parameters`, and `additional_headers` keys of `request_options`.

```python
client.videos.list_videos(
    collection_id="collection_id",
    request_options={
        "additional_query_parameters": {"debug": "true"},
    },
)
```

### Configuring the HTTP client

You can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including support for proxies, custom transports, and other advanced functionality.

```python
import httpx
from pureframe import Pureframe

client = Pureframe(
    token="pf_...",
    base_url="https://api.pureframe.ai",
    httpx_client=httpx.Client(
        proxy="http://my.test.proxy.example.com",
        transport=httpx.HTTPTransport(local_address="0.0.0.0"),
    ),
)
```

## Requirements

Python 3.9 or higher.

## Versioning

This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:

1. Changes that only affect static types, without breaking runtime behavior.
2. Changes to library internals which are technically public but not intended or documented for external use.
3. Changes that we do not expect to impact the vast majority of users in practice.

We are keen for your feedback; please open an [issue](https://github.com/pureframeai/pureframe-python/issues) with questions, bugs, or suggestions.

## Contributing

This library is generated from our OpenAPI specification, so most code changes need to happen at the spec level — see [the contributing documentation](./CONTRIBUTING.md) for details on what to file where.

## License

[MIT](./LICENSE)
