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

# Python SDK

> API reference for interhumanai — the official Python client for auth, upload, stream, and realtime analysis.

First-party Python SDK for the Interhuman API.

Quickstart:

```python theme={null}
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())
```

## Clients & helpers

### AuthClient

Client for the token endpoints (`/v1/auth` and `/v1/client_tokens`).

Args:
environment: Named environment to call. Defaults to `production`.
base\_url: Explicit base URL override (e.g. `http://localhost:8080`).
http\_client: Optional shared `httpx.AsyncClient`. When omitted,
a client is created per request.

**Constructor**

```python theme={null}
AuthClient(*, environment: Optional[Literal['production', 'staging']] = None, base_url: str | None = None, http_client: httpx.AsyncClient | None = None) -> None
```

#### AuthClient.base\_url

```python theme={null}
@property
def base_url(self) -> str
```

The resolved HTTP base URL this client calls.

#### AuthClient.create\_client\_token()

```python theme={null}
async def create_client_token(self, *, api_key: str, scopes: collections.abc.Sequence[Scope | str] | None = None, expires_in: int | None = None, max_duration_seconds: int | None = None, max_bytes: int | None = None, max_concurrent: int | None = None, max_video_seconds: int | None = None, allowed_origins: collections.abc.Sequence[str] | None = None) -> ClientTokenResponse
```

Mint a short-lived, capped client token for direct end-user use.

Call this from a trusted server: the full API key travels in the
request body. Omitted options use the server defaults (scope
`interhumanai.stream`, 300 second lifetime clamped to 60-3600).

Args:
api\_key: The full API key (`ih_...`) minting the token.
scopes: Scopes the client token should carry.
expires\_in: Requested lifetime in seconds (server clamps to 60-3600).
max\_duration\_seconds: Cap on a single live session's duration.
max\_bytes: Cap on bytes accepted across the token's sessions.
max\_concurrent: Cap on concurrent sessions (server default 1).
max\_video\_seconds: Video-seconds budget across all surfaces.
allowed\_origins: Browser origins allowed to use the token.

Returns:
The minted client token and its effective caps.

#### AuthClient.create\_token()

```python theme={null}
async def create_token(self, *, key_id: str, key_secret: str, scopes: collections.abc.Sequence[Scope | str]) -> TokenResponse
```

Exchange API key credentials for a short-lived bearer access token.

Args:
key\_id: The API key id.
key\_secret: The API key secret.
scopes: Scopes to request; must be non-empty and held by the key.

Returns:
The minted token, its lifetime, and the granted scopes.

#### AuthClient.revoke\_client\_token()

```python theme={null}
async def revoke_client_token(self, *, api_key: str, token: str) -> None
```

Revoke a previously minted client token.

Revoking an already-expired token is a no-op and also succeeds.

Args:
api\_key: The API key that minted the token.
token: The client token to revoke.

### InterhumanClient

High-level client for the Interhuman API.

Authenticate either with API key credentials (`key_id` + `key_secret`,
exchanged for short-lived bearer tokens that are refreshed automatically)
or with a pre-issued `access_token` used as-is. Exactly one of the two
must be provided.

The client exposes every public surface: `auth` for token endpoints,
`upload` for complete files, and `stream` / `realtime`
for live WebSocket sessions.

Args:
key\_id: API key id (paired with `key_secret`).
key\_secret: API key secret.
access\_token: Pre-issued bearer token (JWT or client token).
scopes: Scopes requested when exchanging credentials. Defaults to
upload + stream. Ignored when `access_token` is used.
environment: Named environment to call. Defaults to `production`.
base\_url: Explicit base URL override (e.g. `http://localhost:8080`).
http\_client: Optional shared `httpx.AsyncClient` for the HTTP
surfaces. The caller owns its lifecycle.
refresh\_skew\_seconds: How long before expiry managed tokens refresh.

**Constructor**

```python theme={null}
InterhumanClient(*, key_id: str | None = None, key_secret: str | None = None, access_token: str | None = None, scopes: collections.abc.Sequence[Scope | str] | None = None, environment: Optional[Literal['production', 'staging']] = None, base_url: str | None = None, http_client: httpx.AsyncClient | None = None, refresh_skew_seconds: float = 30.0) -> None
```

#### InterhumanClient.get\_token()

```python theme={null}
async def get_token(self) -> str
```

Return the bearer token the client is currently using.

With managed credentials this mints or refreshes as needed; with a
pre-issued `access_token` it returns that token unchanged.

#### InterhumanClient.realtime()

```python theme={null}
def realtime(self) -> RealtimeClient
```

Create a client for one `WS /v0/realtime/analyze` session.

Each call returns a fresh, unconnected client; a client handles a
single session.

#### InterhumanClient.stream()

```python theme={null}
def stream(self, *, api_version: Literal['v1', 'v2'] = 'v1', model: StreamModel | str | None = None) -> StreamClient
```

Create a client for one live stream session.

Each call returns a fresh, unconnected client; a client handles a
single session. `api_version="v1"` (the default) opens
`WS /v1/stream/analyze`, analyzed by Inter-1; `api_version="v2"`
opens `WS /v2/stream/analyze`, analyzed by Inter-2. The protocol and
the events are identical on both.

Args:
api\_version: Which stream endpoint to open, `"v1"` or `"v2"`.
model: The Inter-2 model a `"v2"` session opens on (`inter-2`
when omitted). The credential must carry
`interhumanai.stream.<model>` for it; see
`~interhumanai.StreamClient`.

### RealtimeClient

One live session against `WS /v0/realtime/analyze`.

The realtime endpoint ships under the `v0` path and requires the
`interhumanai.realtime` scope. It offers multi-track analysis
configuration, caller-supplied transcripts, and periodic recommendations.

#### RealtimeClient.send\_transcript()

```python theme={null}
async def send_transcript(self, transcript: collections.abc.Sequence[TranscriptSegment]) -> None
```

Send the latest client-side transcript of the conversation.

Each call fully replaces any previously sent transcript; the latest
transcript is rendered into subsequent recommendation output.

Args:
transcript: Ordered transcript segments (speaker ids are
zero-based).

#### RealtimeClient.update\_config()

```python theme={null}
async def update_config(self, *, analysis_groups: collections.abc.Sequence[AnalysisGroup | str] | None = None, realtime_recommendation_frequency: RealtimeRecommendationFrequency | str | None = None, realtime_recommendation_instructions: str | None = None, goal_dimensions: collections.abc.Sequence[GoalDimension | str] | None = None) -> None
```

Replace the session configuration.

Each update fully replaces the previous configuration; omitted options
reset to their server defaults. The server acknowledges with a
`session.updated` event. The recommendation step's system prompt and model
are managed by Interhuman and cannot be set per session.

Args:
analysis\_groups: Analysis tracks to run (must be non-empty when
provided). Server default when omitted: audio and visual.
The `visual` group reads the picture, so pass `["audio"]`
alone to analyze a stream whose recording carries no video
track: leaving `visual` selected and sending such a stream
stops the session's analysis with a single `ih5001` error,
the audio tracks included. Narrowing to `["audio"]` resumes
analysis from the next window.
realtime\_recommendation\_frequency: How often recommendation output is generated.
realtime\_recommendation\_instructions: Non-empty instructions that
enable recommendations; when omitted or empty, recommendations
are disabled.
goal\_dimensions: Goal dimensions that enable recommendation generation.

### SessionSocketClient

One live analysis session over a WebSocket.

Connect with `connect` (or `async with`), send binary video chunks
with `send_video`, and consume typed server events by iterating the
client with `async for`. Iteration ends when the connection closes;
`close_info` then holds the close code and reason.

Args:
token\_provider: Source of bearer tokens for authentication.
environment: Named environment to connect to. Defaults to
`production`.
base\_url: Explicit HTTP base URL override; converted to `ws(s)://`.

**Constructor**

```python theme={null}
SessionSocketClient(*, token_provider: TokenProvider, environment: Optional[Literal['production', 'staging']] = None, base_url: str | None = None) -> None
```

#### SessionSocketClient.close()

```python theme={null}
async def close(self, code: int = 1000, reason: str = '') -> None
```

Close the WebSocket immediately, discarding in-flight analysis.

Args:
code: WebSocket close code to send.
reason: Optional close reason.

#### SessionSocketClient.close\_info

```python theme={null}
@property
def close_info(self) -> CloseInfo | None
```

Close code and reason once the connection has ended, else `None`.

#### SessionSocketClient.connect()

```python theme={null}
async def connect(self) -> 'SessionSocketClient[EventT]'
```

Open the WebSocket connection and start receiving events.

Returns:
This client, for chaining.

Raises:
InterhumanConfigError: If the client was already connected.
InterhumanError: If the handshake fails (bad credentials or scope,
unreachable host).

#### SessionSocketClient.is\_open

```python theme={null}
@property
def is_open(self) -> bool
```

Whether the connection is currently open.

#### SessionSocketClient.request\_close()

```python theme={null}
async def request_close(self) -> None
```

Ask the server to drain in-flight analysis and end the session.

The server acknowledges with `session.closing`, emits any final
events, sends `session.ended`, and closes the socket normally. Keep
iterating to observe the drain; for an immediate teardown use
`close` instead.

#### SessionSocketClient.send\_video()

```python theme={null}
async def send_video(self, chunk: bytes | bytearray | memoryview) -> None
```

Send one binary video chunk.

The first chunk must carry the container's init header; later chunks
are continuation fragments of the same WebM or fragmented-MP4 stream.
The SDK does not enforce a chunk size; the server rejects chunks above
its limit (32 MB by default, reported in `session.ready` as
`max_segment_size_bytes`) with an `ih6002` error.

Args:
chunk: The raw video bytes to send.

#### SessionSocketClient.url

```python theme={null}
@property
def url(self) -> str
```

The WebSocket URL this client connects to.

Carries the endpoint's handshake parameters and the SDK attribution
query pair alongside any query parameters the configured base URL
already had. The same identity also travels in the handshake's
`X-Interhuman-SDK` header; the two always agree.

#### SessionSocketClient.wait\_closed()

```python theme={null}
async def wait_closed(self) -> CloseInfo
```

Wait until the connection has fully closed.

Returns:
The session's close code and reason.

#### SessionSocketClient.wait\_for\_session\_ready()

```python theme={null}
async def wait_for_session_ready(self) -> EventT
```

Wait for the server's `session.ready` event.

The event is also delivered through iteration; this helper simply
awaits it (or returns it if it already arrived).

Returns:
The `session.ready` event.

Raises:
InterhumanError: If the connection closes before the session
becomes ready. The message carries the server's own `error`
envelope when one arrived before the close -- a session the
server accepts and then refuses, such as `ih1003` -- and the
scope hint otherwise, which is the usual cause when the close
explains nothing itself.

### StaticTokenProvider

Token provider that always returns the same pre-issued token.

**Constructor**

```python theme={null}
StaticTokenProvider(token: str) -> None
```

#### StaticTokenProvider.get\_token()

```python theme={null}
async def get_token(self) -> str
```

Return the configured token.

### StreamClient

One live session against `WS /v1/stream/analyze` or `WS /v2/stream/analyze`.

Send WebM or fragmented-MP4 chunks with `send_video`; consume typed
events with `async for`.

`api_version` selects the endpoint: `"v1"` (the default) is analyzed by
the Inter-1 model and requires the `interhumanai.stream` scope; `"v2"`
is analyzed by an Inter-2 model. The session protocol and the events are
identical on both, and each names the endpoint it actually opened in the
errors it raises: a v2 session reports the Inter-2 stream, a v1 session
the Stream.

On `"v2"` the model is a permission of its own. `model` names the model
the session opens on (`StreamModel.INTER_2` when omitted), sent as the
handshake's `model` query parameter, and the credential must carry
`interhumanai.stream.<model>` for it or the server refuses the session
with `ih2003`. `session.ready` lists, under
`supported_session_config_options.model`, the models the deployment
serves that the credential may select; a later
`update_config(model=...)` switching to one the credential lacks is
answered with a non-fatal `ErrorEvent` (`ih2003`) while the
session continues on its current model. Adding an `audio_format` on
`StreamModel.INTER_2_AUDIO` lets you send raw PCM frames with
`send_audio` instead of container media.

Args:
token\_provider: Source of bearer tokens for authentication.
environment: Named environment to connect to. Defaults to
`production`.
base\_url: Explicit HTTP base URL override; converted to `ws(s)://`.
api\_version: Which stream endpoint to open, `"v1"` or `"v2"`.
model: The Inter-2 model a `"v2"` session opens on. Rejected on
`"v1"`, which offers no selection, and for
`StreamModel.INTER_2_DEEP`, which is an upload model.

**Constructor**

```python theme={null}
StreamClient(*, token_provider: TokenProvider, environment: Optional[Literal['production', 'staging']] = None, base_url: str | None = None, api_version: Literal['v1', 'v2'] = 'v1', model: StreamModel | str | None = None) -> None
```

#### StreamClient.api\_version

```python theme={null}
@property
def api_version(self) -> Literal['v1', 'v2']
```

The stream endpoint this client opens (`"v1"` or `"v2"`).

#### StreamClient.model

```python theme={null}
@property
def model(self) -> str | None
```

The model a `"v2"` session opens on, as named at construction.

`None` on `"v1"` and when the server default (`inter-2`) is left
to apply.

#### StreamClient.send\_audio()

```python theme={null}
async def send_audio(self, frame: bytes | bytearray | memoryview) -> None
```

Send one frame of raw PCM audio.

Only meaningful on `api_version="v2"` after `update_config`
declared an `audio_format` with `StreamModel.INTER_2_AUDIO`; the
server then reads every binary frame as signed 16-bit mono PCM at the
declared sample rate. Frames can be any size and cut at any byte
boundary. Without that declaration a binary frame is container media,
which `send_video` names.

Args:
frame: The raw sample bytes to send.

#### StreamClient.update\_config()

```python theme={null}
async def update_config(self, *, include: collections.abc.Sequence[IncludeFlag | str] | None = None, goal_dimensions: collections.abc.Sequence[GoalDimension | str] | None = None, model: StreamModel | str | None = None, audio_format: RawAudioFormat | None = None) -> None
```

Replace the session configuration.

Each update fully replaces the previous `include` and
`goal_dimensions`; omitted options reset to their server defaults.
`model` and `audio_format` are session state instead: omitting
them keeps the values in force, and a different value is accepted only
before the first media frame. This method never sends an explicit
`null` for either, so a selection cannot be reset through it; open a
new session instead. The server acknowledges with a `session.updated`
event.

Args:
include: Optional sections to include in quality updates.
goal\_dimensions: Goal dimensions that enable feedback generation.
model: The Inter-2 model to analyze the session with, on
`api_version="v2"` only. `StreamModel.INTER_2` (the
default) reads the video; `StreamModel.INTER_2_AUDIO` hears
the audio alone.
audio\_format: Declares that binary frames are raw PCM audio (see
`~interhumanai.RawAudioFormat`), sent with
`send_audio`. Requires `StreamModel.INTER_2_AUDIO`.

### TokenManager

Mints bearer tokens from API key credentials and refreshes them early.

Tokens are cached until `expires_in - refresh_skew_seconds` elapses;
concurrent callers share a single in-flight mint.

Args:
auth\_client: The `AuthClient` used to mint tokens.
key\_id: The API key id.
key\_secret: The API key secret.
scopes: Scopes to request on every mint.
refresh\_skew\_seconds: Seconds before expiry at which the cached token
is considered stale.
clock: Monotonic clock returning seconds; injectable for tests.

**Constructor**

```python theme={null}
TokenManager(*, auth_client: AuthClient, key_id: str, key_secret: str, scopes: collections.abc.Sequence[Scope | str] = (<Scope.UPLOAD: 'interhumanai.upload'>, <Scope.STREAM: 'interhumanai.stream'>), refresh_skew_seconds: float = 30.0, clock: Callable[[], float] = <built-in function monotonic>) -> None
```

#### TokenManager.get\_token()

```python theme={null}
async def get_token(self) -> str
```

Return a valid access token, minting or refreshing when needed.

#### TokenManager.invalidate()

```python theme={null}
def invalidate(self) -> None
```

Drop the cached token so the next call mints a fresh one.

### TokenProvider

Anything that can produce a bearer access token on demand.

**Constructor**

```python theme={null}
TokenProvider(*args, **kwargs)
```

#### TokenProvider.get\_token()

```python theme={null}
async def get_token(self) -> str
```

Return a currently valid bearer access token.

### UploadClient

Client for analyzing complete files.

`analyze` is the v1 route: the file is analyzed inside the request
and the report comes back with the response. `submit`,
`get_job` and `wait_for_job` are the v2 job routes: a file is
accepted as a job, analyzed by an Inter-2 model after the response, and
read back by id until it is `completed` or `failed`.

Args:
token\_provider: Source of bearer tokens for authentication.
environment: Named environment to call. Defaults to `production`.
base\_url: Explicit base URL override.
http\_client: Optional shared `httpx.AsyncClient`. When omitted,
a client is created per request.

**Constructor**

```python theme={null}
UploadClient(*, token_provider: TokenProvider, environment: Optional[Literal['production', 'staging']] = None, base_url: str | None = None, http_client: httpx.AsyncClient | None = None) -> None
```

#### UploadClient.analyze()

```python theme={null}
async def analyze(self, file: Union[bytes, bytearray, memoryview, IO[bytes], str, os.PathLike[str]], *, filename: str | None = None, content_type: str | None = None, include: collections.abc.Sequence[IncludeFlag | str] | None = None, goal_dimensions: collections.abc.Sequence[GoalDimension | str] | None = None, conversation_context: str | None = None) -> AnalysisResult
```

Analyze a complete video file and return the detected signals.

The video must be at least 3 seconds long and at most 32 MB, in one of
the supported containers (mp4, avi, mov, mkv, mpeg-ts, webm).

Args:
file: The video to analyze - raw bytes, an open binary file
object, or a filesystem path.
filename: Filename reported to the API. Defaults to the path or
file object's name, else `video`.
content\_type: MIME type of the file (e.g. `video/mp4`).
include: Optional response sections to include (conversation
quality overall and/or timeline).
goal\_dimensions: Goal dimensions that trigger interaction
feedback. Ignored when `conversation_context` is set.
conversation\_context: Free-text description of the interaction;
when set, it drives feedback generation.

Returns:
The analysis result. Optional sections are only present when
requested.

#### UploadClient.get\_job()

```python theme={null}
async def get_job(self, job_id: str | UploadJob) -> UploadJob
```

Read a job's current envelope from `GET /v2/upload/jobs/{job_id}`.

Args:
job\_id: The job's id, or the envelope `submit` returned.

Returns:
The envelope as it stands: `result` once `completed`, `error`
once `failed`.

Raises:
InterhumanAPIError: `ih4021` (404) when the id is unknown to this
account or the job has expired, among the usual errors.

#### UploadClient.submit()

```python theme={null}
async def submit(self, file: Union[bytes, bytearray, memoryview, IO[bytes], str, os.PathLike[str]], *, model: UploadModel | str, wait_seconds: int = 0, filename: str | None = None, content_type: str | None = None) -> UploadJob
```

Submit a file to `POST /v2/upload/analyze` as an asynchronous job.

The API validates the file and answers with the job envelope before
the analysis runs. With `wait_seconds` above zero the request is held
open for up to that long, and the envelope comes back terminal
(`result` or `error` set) when the job finished in time; otherwise
it comes back `queued` or `running` and `wait_for_job` or
`get_job` reads it later.

For `UploadModel.INTER_2_AUDIO` the file is wav, flac, mp3, m4a, ogg,
or a webm or mp4 with an audio track — at least 3 seconds of media, at
most 32 MB, and no longer than the deployment's maximum duration (30
minutes by default; a longer file raises `ih4004`). The job cuts it
into fixed windows and analyzes each one.

Args:
file: The file to analyze - raw bytes, an open binary file object,
or a filesystem path.
model: The Inter-2 model to analyze with. `INTER_2` and
`INTER_2_DEEP` are valid values the route does not serve yet
and raise `~interhumanai.InterhumanAPIError` (`ih4020`).
wait\_seconds: How long the API may hold the request waiting for
the job to finish. `0` (the default) answers at once. The
deployment bounds it; a larger value raises `ih4005`.
filename: Filename reported to the API. Defaults to the path or
file object's name, else `video`.
content\_type: MIME type of the file (e.g. `audio/wav`).

Returns:
The job envelope. Check `UploadJob.status`.

#### UploadClient.wait\_for\_job()

```python theme={null}
async def wait_for_job(self, job_id: str | UploadJob, *, timeout: float | None = None, poll_interval: float = 2.0) -> UploadJob
```

Poll a job until it is `completed` or `failed`, and return it.

A terminal envelope passed in is returned at once without a request.
A failed job is returned, not raised: read `UploadJob.error`.

Args:
job\_id: The job's id, or an envelope from `submit` or
`get_job`.
timeout: Give up after this many seconds. `None` waits until the
job is terminal.
poll\_interval: Seconds between status reads.

Returns:
The terminal envelope.

Raises:
UploadJobTimeoutError: `timeout` elapsed first. The job keeps
running; the error carries the last envelope read.

## Data models

### AnalysisResult

Response of `POST /v1/upload/analyze`.

Optional sections are only present when requested: `feedback` when goal
dimensions or a conversation context were supplied, `conversation_quality`
when requested via include flags.

| Field                  | Type                               | Required |
| ---------------------- | ---------------------------------- | -------- |
| `signals`              | `list[Signal]`                     | No       |
| `engagement_state`     | `list[EngagementStateEntry]`       | No       |
| `feedback`             | `Optional[Feedback \| NoFeedback]` | No       |
| `conversation_quality` | `ConversationQuality \| None`      | No       |

### ClientTokenResponse

Response of `POST /v1/client_tokens`.

| Field                  | Type                | Required |
| ---------------------- | ------------------- | -------- |
| `access_token`         | `str`               | Yes      |
| `token_type`           | `str`               | Yes      |
| `expires_in`           | `int`               | Yes      |
| `scope`                | `str`               | Yes      |
| `max_duration_seconds` | `int \| None`       | No       |
| `max_bytes`            | `int \| None`       | No       |
| `max_concurrent`       | `int \| None`       | No       |
| `max_video_seconds`    | `int \| None`       | No       |
| `allowed_origins`      | `list[str] \| None` | No       |

### CloseInfo

Close code and reason of a finished WebSocket session.

| Field    | Type  | Required |
| -------- | ----- | -------- |
| `code`   | `int` | Yes      |
| `reason` | `str` | Yes      |

### ConversationQuality

Conversation-quality section of an analysis result.

| Field      | Type                                     | Required |
| ---------- | ---------------------------------------- | -------- |
| `overall`  | `ConversationQualityValues`              | Yes      |
| `timeline` | `list[ConversationQualityTimelineEntry]` | No       |

### ConversationQualityTimelineEntry

Conversation-quality scores for one slice of the video.

| Field    | Type                        | Required |
| -------- | --------------------------- | -------- |
| `start`  | `float`                     | Yes      |
| `end`    | `float`                     | Yes      |
| `values` | `ConversationQualityValues` | Yes      |

### ConversationQualityUpdatedData

Latest conversation-quality scores (sections follow the include flags).

| Field      | Type                                             | Required |
| ---------- | ------------------------------------------------ | -------- |
| `overall`  | `ConversationQualityValues \| None`              | No       |
| `timeline` | `list[ConversationQualityTimelineEntry] \| None` | No       |

### ConversationQualityUpdatedEvent

New conversation-quality scores are available.

| Field            | Type                                      | Required |
| ---------------- | ----------------------------------------- | -------- |
| `timestamp`      | `datetime`                                | Yes      |
| `correlation_id` | `str`                                     | Yes      |
| `type`           | `Literal['conversation_quality.updated']` | Yes      |
| `data`           | `ConversationQualityUpdatedData`          | Yes      |

### ConversationQualityValues

Conversation-quality scores (0-100; 50 means no evidence either way).

| Field           | Type    | Required |
| --------------- | ------- | -------- |
| `quality_index` | `float` | Yes      |
| `clarity`       | `float` | Yes      |
| `authority`     | `float` | Yes      |
| `energy`        | `float` | Yes      |
| `rapport`       | `float` | Yes      |
| `learning`      | `float` | Yes      |

### CoverageDegradedData

Ranges analyzed with partial visual coverage (billed normally).

Unlike `coverage.dropped`, these windows *were* analyzed — the audio
and any decodable video informed the analysis; only the visual coverage
was partial (e.g. a screen share whose keyframe interval exceeds the
analysis window).

| Field    | Type                   | Required                   |
| -------- | ---------------------- | -------------------------- |
| `ranges` | `list[CoverageRange]`  | No                         |
| `reason` | `Literal['video_gap']` | No (default `'video_gap'`) |

### CoverageDegradedEvent

One or more windows were analyzed with partial visual coverage.

| Field            | Type                           | Required |
| ---------------- | ------------------------------ | -------- |
| `timestamp`      | `datetime`                     | Yes      |
| `correlation_id` | `str`                          | Yes      |
| `type`           | `Literal['coverage.degraded']` | Yes      |
| `data`           | `CoverageDegradedData`         | Yes      |

### CoverageDroppedData

Time ranges no analysis covers (not billed).

Emitted when the analysis pipeline sheds buffered video under
backpressure, and when the incoming stream itself skipped ahead — a
client stall whose media never arrived while its recorder clock kept
running. Either way the listed ranges were never analyzed.

| Field    | Type                  | Required |
| -------- | --------------------- | -------- |
| `ranges` | `list[CoverageRange]` | No       |

### CoverageDroppedEvent

Some video was dropped without being analyzed.

| Field            | Type                          | Required |
| ---------------- | ----------------------------- | -------- |
| `timestamp`      | `datetime`                    | Yes      |
| `correlation_id` | `str`                         | Yes      |
| `type`           | `Literal['coverage.dropped']` | Yes      |
| `data`           | `CoverageDroppedData`         | Yes      |

### CoverageRange

A time range of video that was not analyzed.

| Field   | Type    | Required |
| ------- | ------- | -------- |
| `start` | `float` | Yes      |
| `end`   | `float` | Yes      |

### EngagementStateEntry

An engagement state over a time range.

| Field   | Type              | Required |
| ------- | ----------------- | -------- |
| `state` | `EngagementLevel` | Yes      |
| `start` | `float`           | Yes      |
| `end`   | `float`           | Yes      |

### EngagementUpdatedData

The engagement state entered at `start`.

| Field   | Type              | Required |
| ------- | ----------------- | -------- |
| `state` | `EngagementLevel` | Yes      |
| `start` | `float`           | Yes      |

### EngagementUpdatedEvent

The subject's engagement state changed.

| Field            | Type                            | Required |
| ---------------- | ------------------------------- | -------- |
| `timestamp`      | `datetime`                      | Yes      |
| `correlation_id` | `str`                           | Yes      |
| `type`           | `Literal['engagement.updated']` | Yes      |
| `data`           | `EngagementUpdatedData`         | Yes      |

### ErrorData

A session error notice (fatal errors also close the socket).

| Field     | Type          | Required |
| --------- | ------------- | -------- |
| `code`    | `str`         | Yes      |
| `message` | `str`         | Yes      |
| `link`    | `str \| None` | No       |
| `segment` | `int \| None` | No       |

### ErrorEvent

The server reported an error for this session.

| Field            | Type               | Required |
| ---------------- | ------------------ | -------- |
| `timestamp`      | `datetime`         | Yes      |
| `correlation_id` | `str`              | Yes      |
| `type`           | `Literal['error']` | Yes      |
| `data`           | `ErrorData`        | Yes      |

### Feedback

Actionable interaction feedback generated from the analysis.

| Field               | Type                  | Required                  |
| ------------------- | --------------------- | ------------------------- |
| `type`              | `Literal['feedback']` | No (default `'feedback'`) |
| `message`           | `str`                 | Yes                       |
| `active_dimensions` | `list[GoalDimension]` | No                        |
| `primary_signal`    | `SignalType \| None`  | No                        |

### FeedbackGeneratedData

Feedback text generated from the active goal dimensions.

| Field      | Type  | Required |
| ---------- | ----- | -------- |
| `feedback` | `str` | Yes      |

### FeedbackGeneratedEvent

Interaction feedback was generated.

| Field            | Type                            | Required |
| ---------------- | ------------------------------- | -------- |
| `timestamp`      | `datetime`                      | Yes      |
| `correlation_id` | `str`                           | Yes      |
| `type`           | `Literal['feedback.generated']` | Yes      |
| `data`           | `FeedbackGeneratedData`         | Yes      |

### NoFeedback

Explicit indication that no feedback was warranted, with the reason.

| Field    | Type                     | Required                     |
| -------- | ------------------------ | ---------------------------- |
| `type`   | `Literal['no_feedback']` | No (default `'no_feedback'`) |
| `reason` | `str`                    | Yes                          |

### RawAudioFormat

Declares that a session's binary frames are raw audio, not container media.

Signed 16-bit little-endian PCM at `sample_rate`, mono, with no header;
frames may be cut at any byte boundary. Only `inter-2-audio` accepts it.

Attributes:
encoding: Always `pcm_s16le`.
sample\_rate: Samples per second, 16000 or 24000.
channels: Always `1`.

| Field         | Type                    | Required                   |
| ------------- | ----------------------- | -------------------------- |
| `encoding`    | `Literal['pcm_s16le']`  | No (default `'pcm_s16le'`) |
| `sample_rate` | `Literal[16000, 24000]` | No (default `16000`)       |
| `channels`    | `Literal[1]`            | No (default `1`)           |

### RealtimeRecommendationGeneratedData

Generated guidance for the analyzed window.

`text` is the literal `NO_GUIDANCE` when the model had nothing to
suggest for this window.

| Field   | Type    | Required |
| ------- | ------- | -------- |
| `text`  | `str`   | Yes      |
| `start` | `float` | Yes      |
| `end`   | `float` | Yes      |

### RealtimeRecommendationGeneratedEvent

New recommendation output is available.

| Field            | Type                                           | Required |
| ---------------- | ---------------------------------------------- | -------- |
| `timestamp`      | `datetime`                                     | Yes      |
| `correlation_id` | `str`                                          | Yes      |
| `type`           | `Literal['realtime_recommendation.generated']` | Yes      |
| `data`           | `RealtimeRecommendationGeneratedData`          | Yes      |

### RealtimeSessionConfigOptions

Session-config options the realtime endpoint supports.

The recommendation step's system prompt and model are managed by Interhuman and
are not session-selectable, so only the caller-owned options appear here.

| Field                                  | Type                                    | Required |
| -------------------------------------- | --------------------------------------- | -------- |
| `realtime_recommendation_instructions` | `str`                                   | Yes      |
| `realtime_recommendation_frequency`    | `list[RealtimeRecommendationFrequency]` | Yes      |
| `analysis_groups`                      | `list[AnalysisGroup]`                   | Yes      |

### RealtimeSessionReadyData

Limits and supported options of a newly opened realtime session.

| Field                              | Type                           | Required |
| ---------------------------------- | ------------------------------ | -------- |
| `session_idle_timeout_seconds`     | `int`                          | Yes      |
| `session_max_duration_seconds`     | `int`                          | Yes      |
| `max_segment_duration_seconds`     | `float \| None`                | No       |
| `min_segment_size_bytes`           | `int`                          | Yes      |
| `max_segment_size_bytes`           | `int`                          | Yes      |
| `supported_session_config_options` | `RealtimeSessionConfigOptions` | Yes      |

### RealtimeSessionReadyEvent

The session is accepted and ready to receive video.

| Field            | Type                       | Required |
| ---------------- | -------------------------- | -------- |
| `timestamp`      | `datetime`                 | Yes      |
| `correlation_id` | `str`                      | Yes      |
| `type`           | `Literal['session.ready']` | Yes      |
| `data`           | `RealtimeSessionReadyData` | Yes      |

### RealtimeSessionUpdatedData

The realtime session configuration now in effect.

Reports only the caller-owned options. The recommendation system prompt and
model are managed by Interhuman rather than session state, so they are not
part of the acknowledgment.

| Field                                  | Type                                      | Required |
| -------------------------------------- | ----------------------------------------- | -------- |
| `realtime_recommendation_instructions` | `str \| None`                             | No       |
| `realtime_recommendation_frequency`    | `RealtimeRecommendationFrequency \| None` | No       |
| `analysis_groups`                      | `list[AnalysisGroup]`                     | Yes      |

### RealtimeSessionUpdatedEvent

Acknowledgment of a session-config update.

| Field            | Type                         | Required |
| ---------------- | ---------------------------- | -------- |
| `timestamp`      | `datetime`                   | Yes      |
| `correlation_id` | `str`                        | Yes      |
| `type`           | `Literal['session.updated']` | Yes      |
| `data`           | `RealtimeSessionUpdatedData` | Yes      |

### RealtimeSignalDetectedData

A newly detected signal (its `end` is not known yet).

The realtime payload carries no `rationale`. `modality` names the
analyses whose evidence produced the signal: one or both of `"audio"`
and `"visual"`.

| Field         | Type                  | Required |
| ------------- | --------------------- | -------- |
| `signal_type` | `SignalType`          | Yes      |
| `start`       | `float`               | Yes      |
| `probability` | `Probability \| None` | No       |
| `modality`    | `list[str]`           | No       |

### RealtimeSignalDetectedEvent

A social signal was detected.

| Field            | Type                         | Required |
| ---------------- | ---------------------------- | -------- |
| `timestamp`      | `datetime`                   | Yes      |
| `correlation_id` | `str`                        | Yes      |
| `type`           | `Literal['signal.detected']` | Yes      |
| `data`           | `RealtimeSignalDetectedData` | Yes      |

### RealtimeSignalUpdatedData

Updated details for a signal that is still active.

The realtime payload carries no `rationale`. A change to `modality` —
the set of analyses reporting the signal — is one of the things that
emits `signal.updated`.

| Field         | Type                  | Required |
| ------------- | --------------------- | -------- |
| `signal_type` | `SignalType`          | Yes      |
| `start`       | `float`               | Yes      |
| `probability` | `Probability \| None` | No       |
| `modality`    | `list[str]`           | No       |

### RealtimeSignalUpdatedEvent

An active signal's details changed.

| Field            | Type                        | Required |
| ---------------- | --------------------------- | -------- |
| `timestamp`      | `datetime`                  | Yes      |
| `correlation_id` | `str`                       | Yes      |
| `type`           | `Literal['signal.updated']` | Yes      |
| `data`           | `RealtimeSignalUpdatedData` | Yes      |

### SessionClosingData

Drain window granted after a graceful close request.

| Field               | Type  | Required |
| ------------------- | ----- | -------- |
| `max_drain_seconds` | `int` | Yes      |

### SessionClosingEvent

The server accepted a graceful close and is draining.

| Field            | Type                         | Required |
| ---------------- | ---------------------------- | -------- |
| `timestamp`      | `datetime`                   | Yes      |
| `correlation_id` | `str`                        | Yes      |
| `type`           | `Literal['session.closing']` | Yes      |
| `data`           | `SessionClosingData`         | Yes      |

### SessionConfigOptions

Session-config options the stream endpoint supports.

Attributes:
include: The conversation-quality sections a config may include.
goal\_dimensions: The goal dimensions a config may set, or `None`
when feedback is disabled.
model: The models this deployment serves on `WS /v2/stream/analyze`;
`None` on `WS /v1/stream/analyze`, which offers no selection.

| Field             | Type                          | Required |
| ----------------- | ----------------------------- | -------- |
| `include`         | `list[IncludeFlag]`           | No       |
| `goal_dimensions` | `list[GoalDimension] \| None` | No       |
| `model`           | `list[StreamModel] \| None`   | No       |

### SessionEndedData

Why the session ended.

| Field    | Type  | Required |
| -------- | ----- | -------- |
| `reason` | `str` | Yes      |

### SessionEndedEvent

Final envelope of a gracefully ended session.

| Field            | Type                       | Required |
| ---------------- | -------------------------- | -------- |
| `timestamp`      | `datetime`                 | Yes      |
| `correlation_id` | `str`                      | Yes      |
| `type`           | `Literal['session.ended']` | Yes      |
| `data`           | `SessionEndedData`         | Yes      |

### SessionReadyData

Limits and supported options of a newly opened stream session.

| Field                              | Type                   | Required |
| ---------------------------------- | ---------------------- | -------- |
| `session_idle_timeout_seconds`     | `int`                  | Yes      |
| `session_max_duration_seconds`     | `int`                  | Yes      |
| `max_segment_duration_seconds`     | `float \| None`        | No       |
| `min_segment_size_bytes`           | `int`                  | Yes      |
| `max_segment_size_bytes`           | `int`                  | Yes      |
| `supported_session_config_options` | `SessionConfigOptions` | Yes      |

### SessionReadyEvent

The session is accepted and ready to receive video.

| Field            | Type                       | Required |
| ---------------- | -------------------------- | -------- |
| `timestamp`      | `datetime`                 | Yes      |
| `correlation_id` | `str`                      | Yes      |
| `type`           | `Literal['session.ready']` | Yes      |
| `data`           | `SessionReadyData`         | Yes      |

### SessionUpdatedData

The session configuration now in effect.

Attributes:
include: The conversation-quality sections in force.
goal\_dimensions: The goal dimensions in force, or `None` when
feedback is disabled.
model: The model analyzing the session on `WS /v2/stream/analyze`;
`None` on `WS /v1/stream/analyze`.

| Field             | Type                          | Required |
| ----------------- | ----------------------------- | -------- |
| `include`         | `list[IncludeFlag]`           | No       |
| `goal_dimensions` | `list[GoalDimension] \| None` | No       |
| `model`           | `StreamModel \| None`         | No       |

### SessionUpdatedEvent

Acknowledgment of a session-config update.

| Field            | Type                         | Required |
| ---------------- | ---------------------------- | -------- |
| `timestamp`      | `datetime`                   | Yes      |
| `correlation_id` | `str`                        | Yes      |
| `type`           | `Literal['session.updated']` | Yes      |
| `data`           | `SessionUpdatedData`         | Yes      |

### Signal

A detected social signal over a time range.

`modality` names the analysis modalities that detected this signal. When
several tracks detect the same signal, every contributing modality is
included.

| Field         | Type                  | Required |
| ------------- | --------------------- | -------- |
| `type`        | `SignalType`          | Yes      |
| `start`       | `float`               | Yes      |
| `end`         | `float`               | Yes      |
| `probability` | `Probability \| None` | No       |
| `rationale`   | `str \| None`         | No       |
| `modality`    | `list[str]`           | No       |

### SignalDetectedData

A newly detected signal (its `end` is not known yet).

`modality` names the analyses whose evidence produced the signal. For
stream sessions this is `["video"]`.

| Field         | Type                  | Required |
| ------------- | --------------------- | -------- |
| `signal_type` | `SignalType`          | Yes      |
| `start`       | `float`               | Yes      |
| `probability` | `Probability \| None` | No       |
| `rationale`   | `str \| None`         | No       |
| `modality`    | `list[str]`           | No       |

### SignalDetectedEvent

A social signal was detected.

| Field            | Type                         | Required |
| ---------------- | ---------------------------- | -------- |
| `timestamp`      | `datetime`                   | Yes      |
| `correlation_id` | `str`                        | Yes      |
| `type`           | `Literal['signal.detected']` | Yes      |
| `data`           | `SignalDetectedData`         | Yes      |

### SignalEndedData

End time of a signal that is no longer active.

| Field         | Type         | Required |
| ------------- | ------------ | -------- |
| `signal_type` | `SignalType` | Yes      |
| `end`         | `float`      | Yes      |

### SignalEndedEvent

An active signal ended.

| Field            | Type                      | Required |
| ---------------- | ------------------------- | -------- |
| `timestamp`      | `datetime`                | Yes      |
| `correlation_id` | `str`                     | Yes      |
| `type`           | `Literal['signal.ended']` | Yes      |
| `data`           | `SignalEndedData`         | Yes      |

### SignalUpdatedData

Updated details for a signal that is still active.

A change to `modality` — the set of analyses reporting the signal — is
one of the things that emits `signal.updated`.

| Field         | Type                  | Required |
| ------------- | --------------------- | -------- |
| `signal_type` | `SignalType`          | Yes      |
| `start`       | `float`               | Yes      |
| `probability` | `Probability \| None` | No       |
| `rationale`   | `str \| None`         | No       |
| `modality`    | `list[str]`           | No       |

### SignalUpdatedEvent

An active signal's details changed.

| Field            | Type                        | Required |
| ---------------- | --------------------------- | -------- |
| `timestamp`      | `datetime`                  | Yes      |
| `correlation_id` | `str`                       | Yes      |
| `type`           | `Literal['signal.updated']` | Yes      |
| `data`           | `SignalUpdatedData`         | Yes      |

### TokenResponse

Response of `POST /v1/auth`.

| Field          | Type  | Required |
| -------------- | ----- | -------- |
| `access_token` | `str` | Yes      |
| `token_type`   | `str` | Yes      |
| `expires_in`   | `int` | Yes      |
| `scope`        | `str` | Yes      |

### TranscriptSegment

One segment of a conversation transcript.

| Field     | Type    | Required         |
| --------- | ------- | ---------------- |
| `start`   | `float` | Yes              |
| `end`     | `float` | Yes              |
| `text`    | `str`   | Yes              |
| `speaker` | `int`   | No (default `0`) |

### UnknownEvent

A server envelope whose `type` this SDK version does not know.

Newer API versions may add event types; they are surfaced as-is instead of
failing the session.

Attributes:
type: The envelope's `type` discriminator.
raw: The full envelope payload as received.

| Field  | Type             | Required |
| ------ | ---------------- | -------- |
| `type` | `str`            | Yes      |
| `raw`  | `dict[str, Any]` | Yes      |

### UploadJob

The job envelope both v2 upload routes answer with.

Returned by `POST /v2/upload/analyze` and `GET /v2/upload/jobs/{job_id}`
alike. `result` is set only when `status` is `COMPLETED`; `error` only
when it is `FAILED`. `status_url` is the status resource's path,
relative to the API base URL.

| Field        | Type                      | Required |
| ------------ | ------------------------- | -------- |
| `job_id`     | `str`                     | Yes      |
| `status`     | `UploadJobStatus`         | Yes      |
| `model`      | `UploadModel`             | Yes      |
| `created_at` | `datetime`                | Yes      |
| `expires_at` | `datetime`                | Yes      |
| `status_url` | `str`                     | Yes      |
| `result`     | `UploadJobResult \| None` | No       |
| `error`      | `UploadJobError \| None`  | No       |

### UploadJobError

Why an upload job failed, in the API's standard error-body shape.

| Field            | Type          | Required |
| ---------------- | ------------- | -------- |
| `error_id`       | `str`         | Yes      |
| `correlation_id` | `str \| None` | No       |
| `link`           | `str \| None` | No       |
| `message`        | `str \| None` | No       |

### UploadJobResult

The result of a completed upload job: one entry per analyzed window.

| Field              | Type                    | Required |
| ------------------ | ----------------------- | -------- |
| `duration_seconds` | `float`                 | Yes      |
| `window_seconds`   | `float`                 | Yes      |
| `windows`          | `list[UploadJobWindow]` | No       |

### UploadJobWindow

The analysis of one fixed-length window of an upload job's file.

`signals` carry the window's span as their `start` and `end`, and
their `modality` names the evidence the model read (`["audio"]` for
`inter-2-audio`).

| Field               | Type              | Required |
| ------------------- | ----------------- | -------- |
| `index`             | `int`             | Yes      |
| `start_seconds`     | `float`           | Yes      |
| `end_seconds`       | `float`           | Yes      |
| `engagement_status` | `EngagementLevel` | Yes      |
| `signals`           | `list[Signal]`    | No       |

## Enumerations

### AnalysisGroup

Analysis track groups selectable on the realtime API.

| Member   | Value      |
| -------- | ---------- |
| `VISUAL` | `'visual'` |
| `AUDIO`  | `'audio'`  |

### EngagementLevel

Coarse engagement state of the analyzed subject.

| Member       | Value          |
| ------------ | -------------- |
| `ENGAGED`    | `'engaged'`    |
| `NEUTRAL`    | `'neutral'`    |
| `DISENGAGED` | `'disengaged'` |

### GoalDimension

Interaction-goal dimensions used for feedback and conversation quality.

| Member      | Value         |
| ----------- | ------------- |
| `CLARITY`   | `'clarity'`   |
| `AUTHORITY` | `'authority'` |
| `ENERGY`    | `'energy'`    |
| `RAPPORT`   | `'rapport'`   |
| `LEARNING`  | `'learning'`  |

### IncludeFlag

Optional response sections selectable on upload and stream analysis.

| Member                          | Value                             |
| ------------------------------- | --------------------------------- |
| `CONVERSATION_QUALITY_OVERALL`  | `'conversation_quality_overall'`  |
| `CONVERSATION_QUALITY_TIMELINE` | `'conversation_quality_timeline'` |

### Probability

Confidence band attached to a detected signal.

| Member   | Value      |
| -------- | ---------- |
| `HIGH`   | `'high'`   |
| `MEDIUM` | `'medium'` |
| `LOW`    | `'low'`    |

### RealtimeRecommendationFrequency

How often the realtime API generates feedback output.

`HIGH` is roughly every 10 seconds of analyzed video, `MEDIUM` every
20 seconds, and `LOW` every 30 seconds.

| Member   | Value      |
| -------- | ---------- |
| `HIGH`   | `'high'`   |
| `MEDIUM` | `'medium'` |
| `LOW`    | `'low'`    |

### Scope

OAuth-style scopes accepted by the Interhuman API.

A scope names one operation, or one (operation, model) cell of the Inter-2
routes: the bare `UPLOAD` and `STREAM` are the Inter-1 cells
(`POST /v1/upload/analyze`, `WS /v1/stream/analyze`), and each
`UPLOAD_INTER_2*` / `STREAM_INTER_2*` member is one model on the
matching `/v2` route. Any `UPLOAD_INTER_2*` member also allows reading
the account's jobs at `GET /v2/upload/jobs/{job_id}`. No scope implies
another, and there is no stream scope for `inter-2-deep`: it is an
upload-only model.

| Member                 | Value                                 |
| ---------------------- | ------------------------------------- |
| `UPLOAD`               | `'interhumanai.upload'`               |
| `STREAM`               | `'interhumanai.stream'`               |
| `REALTIME`             | `'interhumanai.realtime'`             |
| `UPLOAD_INTER_2`       | `'interhumanai.upload.inter-2'`       |
| `UPLOAD_INTER_2_AUDIO` | `'interhumanai.upload.inter-2-audio'` |
| `UPLOAD_INTER_2_DEEP`  | `'interhumanai.upload.inter-2-deep'`  |
| `STREAM_INTER_2`       | `'interhumanai.stream.inter-2'`       |
| `STREAM_INTER_2_AUDIO` | `'interhumanai.stream.inter-2-audio'` |

### SignalType

Social signals the Interhuman API can detect.

`TENSION` and `FRUSTRATION` name the same underlying negative state,
reported separately so the detecting source stays legible: `TENSION`
comes from the Realtime API's visual track.

| Member          | Value             |
| --------------- | ----------------- |
| `AGREEMENT`     | `'agreement'`     |
| `CONFIDENCE`    | `'confidence'`    |
| `CONFUSION`     | `'confusion'`     |
| `DISAGREEMENT`  | `'disagreement'`  |
| `DISENGAGEMENT` | `'disengagement'` |
| `ENGAGEMENT`    | `'engagement'`    |
| `FRUSTRATION`   | `'frustration'`   |
| `HESITATION`    | `'hesitation'`    |
| `INTEREST`      | `'interest'`      |
| `SKEPTICISM`    | `'skepticism'`    |
| `STRESS`        | `'stress'`        |
| `TENSION`       | `'tension'`       |
| `UNCERTAINTY`   | `'uncertainty'`   |

### StreamModel

Inter-2 models a `WS /v2/stream/analyze` session can select.

`INTER_2` (the default) reads the video. `INTER_2_AUDIO` hears the
audio alone: an audio-only recording is accepted, the video of a recording
that carries one is dropped, and raw PCM frames are accepted once a
`~interhumanai.RawAudioFormat` is declared. `INTER_2_DEEP` is an
upload model; naming it on the stream is refused. `WS /v1/stream/analyze`
offers no selection and rejects any value.

| Member          | Value             |
| --------------- | ----------------- |
| `INTER_2`       | `'inter-2'`       |
| `INTER_2_AUDIO` | `'inter-2-audio'` |
| `INTER_2_DEEP`  | `'inter-2-deep'`  |

### UploadJobStatus

Lifecycle of a `POST /v2/upload/analyze` job.

`QUEUED` and `RUNNING` are transient; `COMPLETED` and `FAILED` are
terminal, and the job stays readable until its `expires_at`.

| Member      | Value         |
| ----------- | ------------- |
| `QUEUED`    | `'queued'`    |
| `RUNNING`   | `'running'`   |
| `COMPLETED` | `'completed'` |
| `FAILED`    | `'failed'`    |

### UploadModel

Inter-2 models a `POST /v2/upload/analyze` job can name.

`INTER_2_AUDIO` is served today. `INTER_2` and `INTER_2_DEEP` are
valid values the route does not serve yet; submitting them raises an
`~interhumanai.InterhumanAPIError` with `error_id` `ih4020`.

| Member          | Value             |
| --------------- | ----------------- |
| `INTER_2`       | `'inter-2'`       |
| `INTER_2_AUDIO` | `'inter-2-audio'` |
| `INTER_2_DEEP`  | `'inter-2-deep'`  |

## Exceptions

### InterhumanAPIError

An error response from the Interhuman API, or a transport failure.

Attributes:
status: HTTP status code, or `0` when the request never reached the
API (network failure).
error\_id: Machine-readable error code from the response body, when the
API supplied one.
correlation\_id: Correlation id of the failed request, when supplied.
link: Documentation link for the error, when supplied.
body: The parsed JSON error body, when one was returned.

### InterhumanConfigError

Raised for client-side misuse, before any network call is made.

Examples: missing credentials, sending on a socket that is not open, or
connecting a client that is already connected.

### InterhumanError

Base class for every error raised by the Interhuman SDK.

### UploadJobTimeoutError

An upload job did not reach a terminal state within the wait the caller allowed.

Raised by `~interhumanai.UploadClient.wait_for_job`. The job itself
is unaffected — it keeps running, and `job` is the last envelope
read, so the caller can keep polling with its `job_id`.

## Functions

### http\_to\_ws\_base\_url()

```python theme={null}
def http_to_ws_base_url(http_base_url: str) -> str
```

Derive the WebSocket base URL from an HTTP base URL.

`https://` becomes `wss://` and `http://` becomes `ws://`; any path
is preserved and a trailing slash is trimmed.

Args:
http\_base\_url: The HTTP base URL to convert.

Returns:
The WebSocket base URL without a trailing slash.

### parse\_realtime\_event()

```python theme={null}
def parse_realtime_event(payload: dict[str, Any]) -> Union[RealtimeSessionReadyEvent, RealtimeSessionUpdatedEvent, SessionClosingEvent, SessionEndedEvent, RealtimeSignalDetectedEvent, RealtimeSignalUpdatedEvent, SignalEndedEvent, TranscriptGeneratedEvent, RealtimeRecommendationGeneratedEvent, CoverageDroppedEvent, CoverageDegradedEvent, ErrorEvent, UnknownEvent]
```

Parse one realtime envelope into its typed event model.

Args:
payload: The decoded JSON envelope (must carry a string `type`).

Returns:
The matching typed event, or `UnknownEvent` for a `type`
this SDK version does not know.

### parse\_stream\_event()

```python theme={null}
def parse_stream_event(payload: dict[str, Any]) -> Union[SessionReadyEvent, SessionUpdatedEvent, SessionClosingEvent, SessionEndedEvent, SignalDetectedEvent, SignalUpdatedEvent, SignalEndedEvent, EngagementUpdatedEvent, ConversationQualityUpdatedEvent, FeedbackGeneratedEvent, CoverageDroppedEvent, CoverageDegradedEvent, ErrorEvent, UnknownEvent]
```

Parse one stream envelope into its typed event model.

Args:
payload: The decoded JSON envelope (must carry a string `type`).

Returns:
The matching typed event, or `UnknownEvent` for a `type`
this SDK version does not know.

### resolve\_http\_base\_url()

```python theme={null}
def resolve_http_base_url(*, base_url: str | None = None, environment: Optional[Literal['production', 'staging']] = None) -> str
```

Resolve the HTTP base URL from an explicit override or a named environment.

Args:
base\_url: Explicit base URL (e.g. `http://localhost:8080`). Takes
precedence over `environment` when provided.
environment: Named environment. Defaults to `production`.

Returns:
The base URL without a trailing slash.

### sdk\_header\_value()

```python theme={null}
def sdk_header_value() -> str
```

Return the `X-Interhuman-SDK` value: `python/<version>`.

## Constants

### DEFAULT\_JOB\_POLL\_INTERVAL\_SECONDS

Type: `float`

```python theme={null}
DEFAULT_JOB_POLL_INTERVAL_SECONDS = 2.0
```

### DEFAULT\_REFRESH\_SKEW\_SECONDS

Type: `float`

```python theme={null}
DEFAULT_REFRESH_SKEW_SECONDS = 30.0
```

### DEFAULT\_SCOPES

Type: `tuple`

```python theme={null}
DEFAULT_SCOPES = (<Scope.UPLOAD: 'interhumanai.upload'>, <Scope.STREAM: 'interhumanai.stream'>)
```

### Environment

Type: `type alias`

```python theme={null}
Environment = Literal['production', 'staging']
```

### HTTP\_BASE\_URLS

Type: `dict`

```python theme={null}
HTTP_BASE_URLS = {'production': 'https://api.interhuman.ai', 'staging': 'https://staging-api.interhuman.ai'}
```

### InteractionFeedback

Type: `type alias`

```python theme={null}
InteractionFeedback = Feedback | NoFeedback
```

### NO\_GUIDANCE

Type: `str`

```python theme={null}
NO_GUIDANCE = 'NO_GUIDANCE'
```

### PcmSampleRate

Type: `type alias`

```python theme={null}
PcmSampleRate = Literal[16000, 24000]
```

### RealtimeEvent

Type: `type alias`

```python theme={null}
RealtimeEvent = Union[RealtimeSessionReadyEvent, RealtimeSessionUpdatedEvent, SessionClosingEvent, SessionEndedEvent, RealtimeSignalDetectedEvent, RealtimeSignalUpdatedEvent, SignalEndedEvent, TranscriptGeneratedEvent, RealtimeRecommendationGeneratedEvent, CoverageDroppedEvent, CoverageDegradedEvent, ErrorEvent, UnknownEvent]
```

### SDK\_HEADER\_NAME

Type: `str`

```python theme={null}
SDK_HEADER_NAME = 'X-Interhuman-SDK'
```

### SDK\_NAME

Type: `str`

```python theme={null}
SDK_NAME = 'python'
```

### STREAM\_ENDPOINT\_PATHS

Type: `dict`

```python theme={null}
STREAM_ENDPOINT_PATHS = {'v1': '/v1/stream/analyze', 'v2': '/v2/stream/analyze'}
```

### StreamApiVersion

Type: `type alias`

```python theme={null}
StreamApiVersion = Literal['v1', 'v2']
```

### StreamEvent

Type: `type alias`

```python theme={null}
StreamEvent = Union[SessionReadyEvent, SessionUpdatedEvent, SessionClosingEvent, SessionEndedEvent, SignalDetectedEvent, SignalUpdatedEvent, SignalEndedEvent, EngagementUpdatedEvent, ConversationQualityUpdatedEvent, FeedbackGeneratedEvent, CoverageDroppedEvent, CoverageDegradedEvent, ErrorEvent, UnknownEvent]
```

### VideoInput

Type: `type alias`

```python theme={null}
VideoInput = Union[bytes, bytearray, memoryview, IO[bytes], str, os.PathLike[str]]
```
