Metadata-Version: 2.4
Name: pura-llm-sdk
Version: 0.1.0
Summary: Official Pura LLM OAuth and delegated inference SDK
License-Expression: LicenseRef-Proprietary
Project-URL: Homepage, https://platform.puradigital.it/en/docs/integrations/sdk
Project-URL: Repository, https://github.com/Pura-Digital/archiveye_suite
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx<1,>=0.28
Dynamic: license-file

# pura-llm-sdk

![Registry version](https://img.shields.io/pypi/v/pura-llm-sdk) · API `2026-10-01` · Proprietary license

Official proprietary Python SDK for registered Pura LLM applications. It uses
OAuth grants and shared account budgets; the internal account virtual key stays
on Pura. The official distribution name is `pura-llm-sdk`.

`usage()` returns monthly/weekly used and remaining percentages, 100% scope
limits and `weekly_share_percent` (25% of the monthly allowance). Subscription
budget/cost amounts are never returned. Only the plan price is monetary;
lifecycle paid-period snapshots also expose percentage allowances.

Install a published release with `pip install pura-llm-sdk==0.1.0`.
For source development, use `pip install ./packages/pura-sdk-python`.
For a verified wheel/source distribution and the TypeScript tarball, follow
[release and integration verification](https://platform.puradigital.it/en/developers/testing).

## OAuth from your backend

```python
from pura_llm import create_authorization, PuraOAuthClient

options = dict(api_base="https://ai.puradigital.it/v1", client_id="YOUR_CLIENT_ID")
url, pending = create_authorization(
    **options, redirect_uri="https://YOUR-APP/pura/callback", locale="it")
# Save `pending` in the user's backend session; redirect the browser to `url`.

oauth = PuraOAuthClient(**options, client_secret="YOUR_SERVER_ONLY_SECRET")
try:
    tokens = oauth.exchange(callback_url, pending)
    user_token_store.set(tokens)
finally:
    oauth.close()
```

Keep the client secret on your backend. Store per-user OAuth tokens encrypted;
persist every refresh-token rotation. A client instance serializes its refreshes;
multiple workers/instances can provide `store.with_refresh_lock()` returning a
context manager backed by a shared database/application lock for that user's
grant. The SDK rereads tokens, skips already completed rotation and saves new
tokens inside that context. Reads and writes must share its transaction/lock
context; always release on failure. A deleted connection returns `None`.
Without this adapter only one client instance is coordinated. Use the adapter to
avoid reuse of an already rotated token.

## Inference


```python
from pura_llm import PuraInferenceClient

pura = PuraInferenceClient(**options, store=user_token_store)
try:
    answer = pura.chat(messages=[{"role":"user", "content":"Ciao"}])
    usage = pura.usage()
    with pura.stream_chat(messages=messages) as lines:
        for line in lines:
            print(line)  # Native SSE event lines, including [DONE].
finally:
    pura.close()
```

Your `TokenStore` implements `get() -> OAuthTokens | None` and `set(tokens)`.
`PuraError.status` and `.code` expose revocation and budget errors. Budget errors
never trigger a wallet fallback. SDK v0 targets API version `2026-10-01`.

## Audio transcription

For certified audio deployments, the same inference client provides:

```python
transcript = pura.transcribe(audio_bytes, filename='recording.wav', language='it')
print(transcript['text'])
```

Model selection is server managed. Do not pass `model=`. Pura maps services
using its `INTEGRATION_CHAT_MODEL` and `INTEGRATION_TRANSCRIPTION_MODEL` environment
variables. Check available services with `capabilities()`.
Files are immutable bytes, nonempty and at most 25,000,000 bytes. Multipart uploads
replay all bytes after access-token refresh. Audio shares chat's OAuth grant,
pending slots and percentage budget; JSON and verbose JSON are supported.
The Pura endpoint stays disabled until a verified EU deployment, cost tracking
and subscription allowlist are configured. No provider or wallet fallback occurs.


## Webhook receiver contract

Delivery requires a configured endpoint and Pura’s separately running webhook
worker. The SDK provides raw-body verification and a framework-neutral handler:

```python
from pura_llm import verify_webhook, create_webhook_handler

# FastAPI: raw_body = await request.body(); never reserialize parsed JSON.
event = verify_webhook(
    raw_body, request.headers,
    signing_secret=server_signing_secret,
    integration_id=registered_client_id,
)
receive = create_webhook_handler(
    store=persistent_event_store,
    on_event=apply_event_in_the_same_transaction,
    signing_secret=server_signing_secret,
    integration_id=registered_client_id,
)
result = receive(raw_body, request.headers)
```

The header is `X-Pura-Signature: t=<unix-seconds>,v1=<hex-HMAC-SHA256>` over
UTF-8 timestamp + `.` + the original bytes, with ±300 seconds default tolerance.
Retries sign a fresh delivery timestamp while keeping event ID, creation time and
app-local sequence. The receiver must bind `integration_id` to its registered app.
`WebhookEventStore.process_once(event_id, handler)` atomically commits the handler
and event-ID persistence, returns False for committed duplicates and rolls back
failures. All workers must use the same persistent store. Return 2xx after commit;
external effects need your own transactional outbox. Sequence handling remains
in your application: discard stale snapshots or detect gaps. Unsupported versions,
invalid signatures and nested credential fields are rejected before dispatch.
There is no account-key decryption API.

OAuth responses include verified `pura_user_id`, persisted alongside tokens. Bind
it to the authenticated local user in your own server-side exchange, then match
lifecycle `user.pura_user_id` to that saved mapping. Do not accept identity from
an unverified browser submission. SDK refresh rejects an account identity change
before saving new tokens or sending inference.
