Metadata-Version: 2.4
Name: psymind
Version: 1.0.0
Summary: Official Python SDK for the PsyMind API v1
Author: PsyMind
License-Expression: MIT
Project-URL: Documentation, https://developers.psymind.app
Project-URL: Repository, https://github.com/EugenBoss/psymind-app/tree/main/sdks/python
Project-URL: Changelog, https://github.com/EugenBoss/psymind-app/blob/main/sdks/python/CHANGELOG.md
Keywords: psymind,api,sdk
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: build==1.3.0; extra == "dev"
Requires-Dist: mypy==2.3.1; extra == "dev"
Requires-Dist: ruff==0.16.4; extra == "dev"
Dynamic: license-file

# psymind

Official Python SDK for PsyMind API v1. Requires Python 3.11 or newer and has no runtime dependencies.

## Quickstart

Install:

```bash
python -m pip install psymind
```

Set the service credential only in the server environment:

```bash
export PSYMIND_API_KEY="your-service-credential"
```

Create a client, create a practice, and wait for its result:

```python
import os
from datetime import UTC, datetime

from psymind import PsyMind, create_idempotency_key

psymind = PsyMind(api_key=os.environ["PSYMIND_API_KEY"])

# Set consent_status to granted only after valid consent has been obtained.
client = psymind.clients.create({
    "external_id": create_idempotency_key("quickstart-client"),
    "preferred_language": "en",
    "timezone": "UTC",
    "consent_status": "granted",
    "consent_version": "your-valid-consent-version",
    "consent_at": datetime.now(UTC).isoformat(),
})

created = psymind.practices.create(
    {
        "client_id": client.data["id"],
        "goal": "Sleep calmly and wake rested",
        "language": "en",
        "generate_audio": True,
    },
    idempotency_key=create_idempotency_key("practice"),
)

result = psymind.practices.wait_until_ready(created.data["id"])
print(result.data["status"], result.request_id)
```

Service credentials are server-only. Do not embed them in browser, mobile, or distributed client applications.

## Resources

The Pythonic client exposes `organizations`, `practitioners`, `clients`, `practices`, `voices`, `methods`, `assignments`, and `outcomes`. Request and response models are generated as `TypedDict` definitions from OpenAPI. `PsyMind.request()` provides low-level access to all 66 public API v1 operations.

The current public contract has no usage or webhook-management routes, so these are intentionally absent.

## Pagination, retries, and errors

List results include `Pagination`. Resource `.all()` helpers follow cursors sequentially, detect loops, enforce `max_pages`, and optionally deduplicate by `id`.

GET requests retry transient failures with bounded exponential jitter; a valid `Retry-After` value is honored in full. POST requests retry only when OpenAPI declares `Idempotency-Key` and the caller supplies a stable key. PATCH, DELETE, authentication POSTs, and non-idempotent POSTs never retry automatically. HTTP 409 is never retried.

Configure `timeout`, `max_retries`, and `retry_base_delay` on the client. Python's synchronous transport applies `timeout` to blocking network I/O, matching standard `urllib` socket-timeout semantics; it is not a wall-clock cap for a slow-drip response or a synchronous credential provider. Polling passes its remaining budget to each request, retries transient GET failures within that budget, and checks the total deadline between calls. `PsyMindError` exposes `code`, `status`, `request_id`, `retry_after`, and `details` where available.

## Organization selection

Service credentials are bound to their organization. A human access token with memberships in multiple organizations may set `organization_id`; the SDK sends `X-PsyMind-Organization-Id`. Tenant binding remains enforced by the API.

## Webhook verification

Pass the exact raw request body, before JSON parsing:

```python
from psymind import verify_webhook

event = verify_webhook(raw_body, headers, webhook_secret)
```

The verifier checks the current `webhook-id`, `webhook-timestamp`, and `webhook-signature` HMAC-SHA256 contract and five-minute timestamp window. Persist processed `webhook-id` values and reject duplicates in your receiver; timestamp validation alone cannot deduplicate a correctly signed delivery.
