> ## Documentation Index
> Fetch the complete documentation index at: https://docs.promptjuggler.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> Run prompts and workflows, manage knowledge bases, and verify webhooks from Python.

The PromptJuggler Python SDK is a thin, typed wrapper over the public REST API. You call
flat, synchronous methods and get back pydantic models — authentication, request building,
and JSON (de)serialization are handled for you.

## Requirements

* Python 3.10 or newer

## Installation

```bash theme={null}
pip install promptjuggler
```

## Set up the client

Create an API key in [Settings](https://promptjuggler.com/settings), then construct the
client once and reuse it:

```python theme={null}
from promptjuggler import PromptJuggler

pj = PromptJuggler("your-api-key")
```

The key is sent as a Bearer token to `https://promptjuggler.com`. Point the SDK at another
host (e.g. for testing) with the `base_url=` argument.

## Per-endpoint usage

Every endpoint in the [API reference](/api/overview) includes a **Python SDK** code sample
showing the exact call — that's the place to look for how to invoke each operation and what
it returns. A typical flow:

```python theme={null}
from promptjuggler import RunStatus

# Trigger a run (async — returns immediately with the run ID)
created = pj.run_prompt("greeting", "production", {"name": "Ada"})

# Poll for the result
run = pj.get_prompt_run(created.id)
if run.status == RunStatus.COMPLETED:
    print(run.output)
```

<Note>
  Methods return pydantic models with typed, validated fields. They are read-only data —
  read the fields, don't write them back.
</Note>

## Error handling

When the API responds with an error status, the SDK raises an `ApiError` carrying the HTTP
status code and the server's message. When the request never reaches the API (DNS failure,
timeout, offline), it raises a `NetworkError`. Both extend the `PromptJugglerError` base
class, so you can catch the whole surface at once.

```python theme={null}
from promptjuggler import ApiError

try:
    revision = pj.get_prompt("does-not-exist", "production")
except ApiError as error:
    error.status_code  # e.g. 404
    str(error)         # the server's error message
```

You never catch a generated-client error — the SDK translates the underlying client's
errors into its own.

## Webhooks

PromptJuggler signs every webhook with the `PromptJuggler-Signature` header. Verify it
against the **raw** request body, before any JSON parsing:

```python theme={null}
import json

from promptjuggler import verify_webhook_signature

payload = request.body  # the raw request body, as text
signature = request.headers.get("PromptJuggler-Signature", "")

if not verify_webhook_signature(payload, signature, "your-webhook-secret"):
    ...  # reject the delivery (e.g. respond 403)

event = json.loads(payload)
```

`verify_webhook_signature` recomputes the HMAC-SHA256 of `{timestamp}.{raw_body}`, compares
it in constant time, and rejects deliveries whose timestamp falls outside a tolerance window
(default 300s) to prevent replay. Widen or narrow it with the `tolerance=` argument.
