Metadata-Version: 2.4
Name: interhumanai
Version: 0.4.1
Summary: First-party Python SDK for the Interhuman API: upload, stream, and real-time social-signal analysis.
Project-URL: Homepage, https://docs.interhuman.ai
Project-URL: Repository, https://github.com/interhumanai/interhuman-api
Project-URL: Changelog, https://github.com/interhumanai/interhuman-api/blob/main/sdk/python/CHANGELOG.md
Author: Interhuman AI
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: analysis,interhuman,sdk,social-signals,video
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.7.0
Requires-Dist: websockets>=13.0
Description-Content-Type: text/markdown

# Interhuman Python SDK

`interhumanai` is the first-party Python client for the
[Interhuman API](https://docs.interhuman.ai). It wraps authentication, client
tokens, video upload analysis, and the live stream and real-time WebSocket
protocols behind a typed, asyncio-first API — no hand-rolled token exchange,
multipart bodies, or WebSocket envelope parsing.

## Installation

```bash
pip install interhumanai
```

Requires Python 3.10+. Runtime dependencies: `httpx`, `websockets`, `pydantic`.

The SDK is asyncio-first: every network call is a coroutine. Use `asyncio.run()`
in scripts and top-level `await` in notebooks. The live stream and real-time
surfaces are inherently event-driven, so a single async API keeps every surface
consistent (and mirrors the promise-based TypeScript SDK).

## Quickstart

```python
import asyncio
from interhumanai import InterhumanClient

async def main() -> None:
    client = InterhumanClient(key_id="...", key_secret="...")
    result = await client.upload.analyze("meeting.mp4")
    for signal in result.signals:
        print(signal.type.value, signal.start, signal.end)

asyncio.run(main())
```

## Authentication

Two ways to authenticate:

- **API key credentials** (`key_id` + `key_secret`): the client exchanges them
  at `POST /v1/auth` for a short-lived bearer token and refreshes it
  automatically before expiry.
- **Pre-issued token** (`access_token`): a JWT or client token used as-is.

```python
from interhumanai import InterhumanClient, Scope

# Managed credentials (recommended for servers)
client = InterhumanClient(key_id="...", key_secret="...", scopes=[Scope.UPLOAD, Scope.STREAM])

# Pre-issued bearer token
client = InterhumanClient(access_token="eyJ...")
```

The standalone `AuthClient` exposes the token endpoints directly, and
`TokenManager` / `StaticTokenProvider` are available when you need to plug a
custom token source into the lower-level clients.

## Client tokens (browser / untrusted clients)

Mint short-lived, capped tokens server-side so untrusted clients never see the
API key:

```python
from interhumanai import AuthClient, Scope

auth = AuthClient()
token = await auth.create_client_token(
    api_key="ih_...",
    scopes=[Scope.STREAM],
    expires_in=300,            # clamped to 60-3600 seconds by the API
    max_concurrent=1,
    max_video_seconds=600,
    allowed_origins=["https://app.example.com"],
)
await auth.revoke_client_token(api_key="ih_...", token=token.access_token)
```

## Upload API

Analyze a complete video file (mp4, avi, mov, mkv, mpeg-ts, or webm; at least
3 seconds, at most 32 MB):

```python
from interhumanai import GoalDimension, IncludeFlag

result = await client.upload.analyze(
    "meeting.mp4",                                        # path, bytes, or file object
    include=[IncludeFlag.CONVERSATION_QUALITY_OVERALL],   # optional sections
    goal_dimensions=[GoalDimension.CLARITY],              # enables feedback
)
```

The typed `AnalysisResult` carries `signals`, `engagement_state`, and — when
requested — `feedback` and `conversation_quality`.

## Stream API

One `StreamClient` handles one live session against `WS /v1/stream/analyze`.
Send binary WebM or fragmented-MP4 chunks and consume typed events with
`async for`:

```python
from interhumanai import IncludeFlag, SignalDetectedEvent

async with client.stream() as session:
    await session.wait_for_session_ready()
    await session.update_config(include=[IncludeFlag.CONVERSATION_QUALITY_OVERALL])
    await session.send_video(first_chunk)   # first chunk carries the container header
    await session.request_close()           # graceful drain; close() tears down immediately
    async for event in session:
        if isinstance(event, SignalDetectedEvent):
            print(event.data.signal_type.value, event.data.start)
```

Iteration ends when the connection closes; `session.close_info` then holds the
close code and reason. Event types this SDK version does not know arrive as
`UnknownEvent` instead of failing the session.

## Real-Time API

`RealtimeClient` targets `WS /v0/real-time/analyze` — a temporary `v0` public
release that accepts either the stream or the realtime scope. On top of the
stream protocol it adds multi-track analysis configuration, client transcripts,
and transcript/synthesis output (it never emits engagement or
conversation-quality events):

```python
from interhumanai import AnalysisGroup, SynthesisFrequency, SynthesisGeneratedEvent, TranscriptSegment

async with client.realtime() as session:
    await session.wait_for_session_ready()
    await session.update_config(
        analysis_groups=[AnalysisGroup.AUDIO, AnalysisGroup.VISUAL],
        synthesis_frequency=SynthesisFrequency.MEDIUM,
        synthesis_prompt="Coach the presenter.",   # non-empty prompt enables synthesis
    )
    await session.send_transcript([TranscriptSegment(start=0.0, end=2.0, text="Hi.", speaker=0)])
    async for event in session:
        if isinstance(event, SynthesisGeneratedEvent):
            print(event.data.text)
```

## Errors

Three surfaces, mirroring the API:

- `InterhumanAPIError` — raised for non-2xx HTTP responses (with `status`,
  `error_id`, `correlation_id`, `link`) and for transport failures
  (`status == 0`).
- `InterhumanConfigError` — raised for client-side misuse before any network
  call (missing credentials, sending on a closed session, double connect).
- WebSocket `error` **envelopes** are delivered as `ErrorEvent`s through
  iteration, not raised — fatal ones are followed by the connection closing.

## Environments

```python
InterhumanClient(key_id=..., key_secret=...)                          # production (default): api.interhuman.ai
InterhumanClient(key_id=..., key_secret=..., environment="staging")   # staging-api.interhuman.ai
InterhumanClient(key_id=..., key_secret=..., base_url="http://localhost:8080")  # local override
```

WebSocket URLs are derived automatically (`https://` → `wss://`).

## Examples

Runnable scripts for every flow live in [`examples/`](examples/): `auth.py`,
`client_tokens.py`, `upload.py`, `stream.py`, and `realtime.py`.

## Development

The SDK lives in the [interhuman-api](https://github.com/interhumanai/interhuman-api)
repository under `sdk/python/`. Its tests live in `tests/sdk/python/` and run
with the repository's main suite:

```bash
uv run pytest tests/sdk/python
```

## Releasing

This package is versioned in **lockstep** with the TypeScript SDK
(`@interhumanai/sdk`): both always carry the same version and release together
from a single workflow. Publishing is automatic and deploy-gated: bump
`__version__` in `src/interhumanai/_version.py` **and** the `version` in
`sdk/typescript/package.json` to the same value (semver), add a `CHANGELOG.md`
entry to each package, and merge to `main`. After the `Deploy` workflow
succeeds, the `SDK release` workflow builds, tests, and publishes both packages
(idempotently per registry), then tags the commit `sdk-v<version>`. See
[`docs/sdk-release.md`](../../docs/sdk-release.md) for details.

## License

Apache-2.0
