---
title: Python SDK
description: The official Clipia Python SDK (PyPI clipia 1.5.0) — Clipia and AsyncClipia clients, the generation queue, catalog and price estimates, webhook checks, errors.
icon: FileCode
---

The official Clipia Python client is published on PyPI as `clipia`. It's built on `httpx`, runs on Python 3.9+ and provides a synchronous `Clipia` and an asynchronous `AsyncClipia` with the same methods. It covers the generation queue (`submit`, `status`, `result`), the high-level `subscribe`, the model catalog with price estimates, and account data.

<Callout type="info" title="Package">
The package and module are both called `clipia`, current version 1.5.0. The base URL is `https://api.clipia.ai`. The SDK doesn't read environment variables on its own: pass the key explicitly as `api_key`.
</Callout>

## Quickstart [#quickstart]

<Steps>

<Step>

### Install

```bash
pip install clipia
```

</Step>

<Step>

### Client

```python
import os
from clipia import Clipia

# Read the key from an environment variable; don't hard-code it
clipia = Clipia(api_key=os.environ["CLIPIA_KEY"])
```

Besides `api_key`, the constructor takes `base_url` (`https://` only), an HTTP `timeout` (60 seconds by default), `max_retries` (how many times `submit` retries a transient failure, default 2; `0` disables retries), `retry_delay` (the base pause between retries, default 0.5 seconds) and `transport` — a custom `httpx` transport for tests. Use the client as a context manager (`with Clipia(...) as clipia:`) or close it with `clipia.close()`.

</Step>

<Step>

### Generate and wait for the result

`subscribe` queues the job and polls its status until it finishes:

```python
out = clipia.subscribe(
    "nano-banana-2",
    input={"prompt": "a neon city in the rain, cinematic lighting"},
    on_queue_update=lambda s: print(s.status),
    poll_interval=1.0,   # default, seconds
    timeout=600.0,       # default, seconds
)

if out.status == "COMPLETED":
    print(out.output)  # links to the finished media
    print(out.cost)    # cost in credits
else:
    print(out.error)
```

</Step>

</Steps>

`subscribe` also returns on `FAILED`, so check `status`. The client retries transient polling errors (network, `429`, `5xx`) on its own. If the job doesn't finish within `timeout`, it raises `ClipiaTimeoutError`; the job keeps running and you can still fetch it by `request_id`.

## Manual queue control [#queue]

```python
job = clipia.submit(
    "nano-banana-2",
    input={"prompt": "..."},
    webhook_url="https://example.com/clipia-webhook",  # optional
)

status = clipia.status(job.request_id)
result = clipia.result(job.request_id)
# result.pending is True while the job is IN_QUEUE or IN_PROGRESS
```

<Callout type="info" title="Idempotency key">
The SDK generates a new `Idempotency-Key` for every `submit` call and retries transient failures — a network error, `429`, `500`, `502`, `503`, `504` — up to `max_retries` times with the same key, honoring `Retry-After`: an internal retry never creates a second job. A new `submit` call in your code, however, gets a new key. To make your own retry land on the same job, pass `idempotency_key` and reuse it on the retry — see [Idempotency](/en/docs/api-reference/idempotency).
</Callout>

## Catalog, price and account [#catalog]

```python
catalog = clipia.models.list()        # {"data": [...]}
model = clipia.models.get("nano-banana-2")

estimate = clipia.models.estimate("nano-banana-2", {"prompt": "a neon city"})
print(estimate.credits)

account = clipia.account.get()
# {"account_id": ..., "balance": {"credits": ...}, "usage_30d": {"requests": ..., "credits_spent": ...}}
```

The catalog and account come back as dictionaries (`TypedDict`); queue and estimate responses are objects with attributes. `models.list()` can include LLM models without a `slug` — see [Model catalog](/en/docs/api-reference/models).

## Ignored parameters [#warnings]

If `input` contains a key the model will not apply (a typo, a parameter of another model), `submit` and `models.estimate` accept the request as usual and return the key in `warnings` — one entry per key. When every parameter is accepted, the list is empty.

```python
# job is the submit response from the section above
for w in job.warnings:
    print(w.code, w.parameter, w.message)
    # unknown_parameter aspect_ration Parameter "aspect_ration" is not recognized and was ignored. …

# The same check before you run anything: an estimate charges nothing
estimate = clipia.models.estimate("nano-banana-2", {"prompt": "..."})
print(estimate.warnings)  # [] — every parameter was accepted
```

The `InputWarning` class and the `warnings` attribute were added in version 1.5.0. `subscribe` returns the generation result, which carries no warnings: call `submit` or `models.estimate` to read them. The `code` values and the other rules are in [Ignored parameters](/en/docs/api-reference/submit#warnings).

## Async client [#async]

```python
import asyncio
from clipia import AsyncClipia

async def main():
    async with AsyncClipia(api_key=os.environ["CLIPIA_KEY"]) as clipia:
        out = await clipia.subscribe("nano-banana-2", input={"prompt": "a cat"})
        print(out.output)

asyncio.run(main())
```

## Verifying webhooks [#webhooks]

```python
from clipia.webhooks import verify_signature

ok = verify_signature(
    secret=os.environ["CLIPIA_WEBHOOK_SECRET"],
    headers=request.headers,
    body=raw_body,           # the raw request body, str or bytes
    tolerance_seconds=300,   # default
)
```

The function returns `True` or `False` and never raises on malformed input. Each API key has its own webhook secret — see [Webhooks](/en/docs/api-reference/webhooks).

## Error handling [#errors]

Any non-2xx response becomes a `ClipiaApiError` with `status`, `code`, `type`, `message` and, when the server sent `Retry-After`, `retry_after` in seconds. `ClipiaTimeoutError` is its subclass for an expired `timeout` in `subscribe`: `status` is 0, `code` is `poll_timeout`, and `request_id` holds the id of the job, which keeps running.

```python
from clipia import ClipiaApiError

try:
    clipia.submit("nano-banana-2", input={"prompt": "..."})
except ClipiaApiError as e:
    print(e.status, e.code, e.type, e.message)
```

Error codes are listed on the [Errors](/en/docs/api-reference/errors) page.

## Sandbox [#sandbox]

A key prefixed `clipia_test_` returns a canned result instantly with no credits charged. See [Sandbox](/en/docs/getting-started/sandbox).
