# Python SDK

```bash
pip install veqen
```

Python 3.10+, no dependencies. Source: [github.com/Keyveve/veqen-sdks](https://github.com/Keyveve/veqen-sdks), MIT licensed.

## Configure

```python
import veqen

veqen.init()  # optional: the first wrapped call configures from the environment
```

| Argument | Environment variable | Default |
|---|---|---|
| `api_key` | `VEQEN_API_KEY` | none: price locally, send nothing |
| `endpoint` | `VEQEN_ENDPOINT` | `https://veqen-app.com` |
| `environment` | `VEQEN_ENVIRONMENT` | none (sets the `environment` tag) |
| `ledger_file` | `VEQEN_LEDGER_FILE` | none (JSON lines, one per call) |
| `flush_interval` | | `2.0` seconds between background sends |
| `budget_poll` | | `30.0` seconds between cloud budget refreshes |

## Wrap a client

```python
from anthropic import Anthropic
from google import genai
from openai import AsyncOpenAI, OpenAI

openai_client = veqen.wrap(OpenAI(), feature="search")
async_client = veqen.wrap(AsyncOpenAI())
claude = veqen.wrap(Anthropic(), residency="us")      # US-only processing is priced at 1.1x
gemini = veqen.wrap(genai.Client())
```

Keyword arguments to `wrap()` become default tags for every call through that client. The wrapper instruments:

| Client | Methods |
|---|---|
| OpenAI, Azure OpenAI and every OpenAI-compatible API | `chat.completions.create` and `.stream`, `responses.create` and `.stream`, `embeddings.create`, `images.generate`, `images.edit`, `audio.speech.create`, `audio.transcriptions.create`, `audio.translations.create` |
| Anthropic | `messages.create`, `messages.stream`, `beta.messages.create` |
| Google GenAI | `models.generate_content`, `models.generate_content_stream`, and the `aio` versions |

Image calls are priced per image at the requested size and quality (or by image tokens, when reported), speech by characters, and transcription by seconds of audio or tokens.

An OpenAI client pointed at another provider is priced as that provider, read from its base URL: Together, Fireworks, Groq, DeepInfra, Cerebras, Azure AI Foundry and the rest of the [price book's hosts](/docs/price-book#matching-model-names-and-hosts). For your own server, name the provider:

```python
together = veqen.wrap(OpenAI(base_url="https://api.together.xyz/v1", api_key=...), project="research")
local = veqen.wrap(OpenAI(base_url="http://vllm.internal:8000/v1"), provider="self-hosted")
```

Everything else on the client passes straight through. Streams are priced when they finish; for OpenAI chat streams, the wrapper turns on `stream_options.include_usage` so the provider reports usage. Calls whose response carries no usage aren't recorded.

## Tag calls

```python
with veqen.tags(customer=org.id, user=user.id):
    answer = openai_client.responses.create(model="gpt-5-mini", input=question)
```

Tags nest, and they follow `asyncio` tasks and `asyncio.to_thread` calls started inside the block (they live in a `ContextVar`). Threads you start yourself don't inherit them: set tags inside the thread. See [Tags](/docs/tags).

## Budgets

```python
veqen.budget(5.00, customer="*")              # every customer: $5 a month, then BudgetExceeded
veqen.budget(200, feature="agent", period="day", mode="warn")
veqen.budget(1_000, project="eval-harness")       # scopes: customer, feature, user, project

try:
    openai_client.responses.create(...)
except veqen.BudgetExceeded as exc:
    print(exc.scope, exc.value, exc.limit_usd)
```

Local budgets count spend in this process. Budgets created in the dashboard are shared by every process using your key; the SDK refreshes them every 30 seconds and enforces them the same way. See [Budgets](/docs/budgets).

## Record a call by hand

For calls the wrapper can't see (batch results, gateways, other languages):

```python
veqen.record(
    "claude-sonnet-4-5",
    provider="anthropic",
    input_tokens=1200, output_tokens=300, cache_read_tokens=8000,
    batch=True,
    customer="acme", feature="nightly-summary",
)
```

Models billed per unit take `units` (images, seconds of audio or video, characters, queries or pages) and, for images and video, the `variant`:

```python
veqen.record("veo-3.1-generate-001", provider="google", units=8, variant="1080p", project="ads")
veqen.record("flux-pro-1.1", provider="bfl", units=4, feature="catalog")
veqen.record("nova-3", provider="deepgram", units=1800, project="call-analytics")   # 30 minutes
```

Pass `cost_usd=` for a model the price book doesn't know, or for a call that reports a cost but no usage (fal and Replicate jobs often do); it's kept as reported. Prices for your own models go in [custom prices](/docs/custom-prices).

## Price without recording

```python
veqen.cost("gpt-4o", 1000, 500)                        # 0.0075
veqen.cost("gpt-image-1", units=1, variant="high/1024x1024")
veqen.cost("whisper-1", units=600)                       # ten minutes of audio
veqen.cost("claude-sonnet-4-5", 2000, 400, cache_read_tokens=10_000, residency="us")
price = veqen.lookup("us.anthropic.claude-sonnet-4-5-20250929-v1:0")   # Bedrock ids resolve too
price.rates["standard"], price.retires
```

## Serverless and shutdown

Events are sent every two seconds and at interpreter exit. In AWS Lambda, Cloud Functions or similar, call `veqen.flush()` before returning: it waits up to five seconds and returns `True` once everything is sent.

## Integrations

LangChain, LiteLLM and the OpenAI Agents SDK: see [Framework integrations](/docs/integrations).
