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

# Types & Dataclasses

> Python types and dataclasses for the Odyssey client.

## Video Types

### VideoFrame

Video frame data received from the stream.

```python theme={null}
@dataclass(frozen=True, slots=True)
class VideoFrame:
    data: np.ndarray      # RGB uint8 array, shape (height, width, 3)
    width: int            # Frame width in pixels
    height: int           # Frame height in pixels
    timestamp_ms: int     # Presentation timestamp in milliseconds
```

| Property       | Type         | Description                                     |
| -------------- | ------------ | ----------------------------------------------- |
| `data`         | `np.ndarray` | RGB uint8 array with shape `(height, width, 3)` |
| `width`        | `int`        | Frame width in pixels                           |
| `height`       | `int`        | Frame height in pixels                          |
| `timestamp_ms` | `int`        | Presentation timestamp in milliseconds          |

**Example usage:**

```python theme={null}
import cv2
from PIL import Image

def on_frame(frame: VideoFrame) -> None:
    # OpenCV (note: OpenCV uses BGR)
    cv2.imshow("video", cv2.cvtColor(frame.data, cv2.COLOR_RGB2BGR))

    # PIL
    image = Image.fromarray(frame.data)

    # Headless processing
    processed = some_ml_model(frame.data)
```

## Recording Types

### Recording

Recording data with presigned URLs for a stream.

```python theme={null}
@dataclass(frozen=True, slots=True)
class Recording:
    stream_id: str              # Unique stream identifier
    video_url: str | None       # Presigned URL for video file
    events_url: str | None      # Presigned URL for events JSON
    thumbnail_url: str | None   # Presigned URL for thumbnail image
    preview_url: str | None     # Presigned URL for preview video
    frame_count: int | None     # Total number of frames
    duration_seconds: float | None  # Duration in seconds
```

| Property           | Type            | Description                              |
| ------------------ | --------------- | ---------------------------------------- |
| `stream_id`        | `str`           | Unique stream identifier                 |
| `video_url`        | `str \| None`   | Presigned URL for video file (MP4)       |
| `events_url`       | `str \| None`   | Presigned URL for events log (JSONL)     |
| `thumbnail_url`    | `str \| None`   | Presigned URL for thumbnail image (JPEG) |
| `preview_url`      | `str \| None`   | Presigned URL for preview video (MP4)    |
| `frame_count`      | `int \| None`   | Total number of frames                   |
| `duration_seconds` | `float \| None` | Duration in seconds                      |

<Note>
  URLs are valid for a limited time (typically 1 hour).
</Note>

### StreamRecordingInfo

Summary info for a stream recording in a list.

```python theme={null}
@dataclass(frozen=True, slots=True)
class StreamRecordingInfo:
    stream_id: str              # Unique stream identifier
    width: int                  # Video width in pixels
    height: int                 # Video height in pixels
    started_at: str             # ISO 8601 timestamp
    ended_at: str | None        # ISO 8601 timestamp or None if active
    duration_seconds: float | None  # Duration in seconds
```

| Property           | Type            | Description                            |
| ------------------ | --------------- | -------------------------------------- |
| `stream_id`        | `str`           | Unique stream identifier               |
| `width`            | `int`           | Video width in pixels                  |
| `height`           | `int`           | Video height in pixels                 |
| `started_at`       | `str`           | ISO 8601 timestamp when stream started |
| `ended_at`         | `str \| None`   | ISO 8601 timestamp when stream ended   |
| `duration_seconds` | `float \| None` | Duration in seconds                    |

### StreamRecordingsList

Paginated list of stream recordings.

```python theme={null}
@dataclass(frozen=True, slots=True)
class StreamRecordingsList:
    recordings: list[StreamRecordingInfo]  # List of recording info
    total: int                              # Total recordings available
    limit: int                              # Max per request
    offset: int                             # Recordings skipped
```

| Property     | Type                        | Description                 |
| ------------ | --------------------------- | --------------------------- |
| `recordings` | `list[StreamRecordingInfo]` | List of recording summaries |
| `total`      | `int`                       | Total recordings available  |
| `limit`      | `int`                       | Limit used in request       |
| `offset`     | `int`                       | Offset used in request      |

## Simulate API Types

<Info>
  Simulate API types were added in v1.0.0
</Info>

### ScriptEntry

An entry in a simulation script.

```python theme={null}
from typing import TypedDict, NotRequired

class StartAction(TypedDict):
    prompt: str
    image: NotRequired[str | bytes]  # Optional image for image-to-video

class InteractAction(TypedDict):
    prompt: str

class ScriptEntry(TypedDict):
    timestamp_ms: int                    # When this action occurs (milliseconds from start)
    start: NotRequired[StartAction]      # Begin a new stream
    interact: NotRequired[InteractAction] # Send an interaction
    end: NotRequired[dict]               # End the stream (empty dict)
```

| Property       | Type                                    | Description                                       |
| -------------- | --------------------------------------- | ------------------------------------------------- |
| `timestamp_ms` | `int`                                   | When this action occurs (milliseconds from start) |
| `start`        | `{ prompt: str, image?: str \| bytes }` | Begin a new stream with initial prompt            |
| `interact`     | `{ prompt: str }`                       | Send an interaction prompt                        |
| `end`          | `{}`                                    | End the current stream (empty dict)               |

### SimulationStream

Output stream from a simulation job, including recording artifact URLs.

```python theme={null}
@dataclass(frozen=True, slots=True)
class SimulationStream:
    stream_id: str                    # Unique stream identifier
    video_url: str | None             # Presigned URL for the video file
    events_url: str | None            # Presigned URL for the events JSON
    thumbnail_url: str | None         # Presigned URL for the thumbnail image
    preview_url: str | None           # Presigned URL for the preview video
    frame_count: int | None           # Total number of frames
    duration_seconds: float | None    # Duration of the video in seconds
    script_index: int                 # Index of the script in batch mode (0 for single script)
```

| Property           | Type            | Description                                             |
| ------------------ | --------------- | ------------------------------------------------------- |
| `stream_id`        | `str`           | Unique stream identifier                                |
| `video_url`        | `str \| None`   | Presigned URL for the video file (MP4)                  |
| `events_url`       | `str \| None`   | Presigned URL for the events log (JSONL)                |
| `thumbnail_url`    | `str \| None`   | Presigned URL for the thumbnail image (JPEG)            |
| `preview_url`      | `str \| None`   | Presigned URL for the preview video (MP4)               |
| `frame_count`      | `int \| None`   | Total number of frames in the video                     |
| `duration_seconds` | `float \| None` | Duration of the video in seconds                        |
| `script_index`     | `int`           | Index of the script in batch mode (0 for single script) |

### SimulationJobInfo

Summary information for a simulation job in a list.

```python theme={null}
@dataclass(frozen=True, slots=True)
class SimulationJobInfo:
    job_id: str                       # Unique identifier for the job
    status: str                       # Current job status
    priority: str                     # Job priority
    created_at: str                   # ISO timestamp when job was created
    completed_at: str | None          # ISO timestamp when job completed
    error_message: str | None         # Error message if job failed
```

| Property        | Type          | Description                                                                                    |
| --------------- | ------------- | ---------------------------------------------------------------------------------------------- |
| `job_id`        | `str`         | Unique identifier for the job                                                                  |
| `status`        | `str`         | Current job status (`pending`, `dispatched`, `processing`, `completed`, `failed`, `cancelled`) |
| `priority`      | `str`         | Job priority                                                                                   |
| `created_at`    | `str`         | ISO timestamp when job was created                                                             |
| `completed_at`  | `str \| None` | ISO timestamp when job completed                                                               |
| `error_message` | `str \| None` | Error message if job failed                                                                    |

### SimulationJobDetail

Detailed information about a simulation job.

```python theme={null}
@dataclass(frozen=True, slots=True)
class SimulationJobDetail:
    job_id: str                       # Unique identifier for the job
    status: SimulationJobStatus       # Current job status
    priority: str                     # Job priority
    created_at: str                   # ISO timestamp when job was created
    dispatched_at: str | None         # ISO timestamp when job was dispatched
    started_at: str | None            # ISO timestamp when processing started
    completed_at: str | None          # ISO timestamp when job completed
    error_message: str | None         # Error message if job failed
    assigned_region: str | None       # Region where the job is being processed
    retry_count: int                  # Number of times the job has been retried
    streams: list[SimulationStream]   # Output streams from the simulation
    estimated_wait_minutes: float | None  # Estimated wait time in minutes
```

| Property                 | Type                     | Description                                                                                    |
| ------------------------ | ------------------------ | ---------------------------------------------------------------------------------------------- |
| `job_id`                 | `str`                    | Unique identifier for the job                                                                  |
| `status`                 | `SimulationJobStatus`    | Current job status (`pending`, `dispatched`, `processing`, `completed`, `failed`, `cancelled`) |
| `priority`               | `str`                    | Job priority                                                                                   |
| `created_at`             | `str`                    | ISO timestamp when job was created                                                             |
| `dispatched_at`          | `str \| None`            | ISO timestamp when the job was dispatched to a worker                                          |
| `started_at`             | `str \| None`            | ISO timestamp when processing started                                                          |
| `completed_at`           | `str \| None`            | ISO timestamp when job completed                                                               |
| `error_message`          | `str \| None`            | Error message if job failed                                                                    |
| `assigned_region`        | `str \| None`            | Region where the job is being processed                                                        |
| `retry_count`            | `int`                    | Number of times the job has been retried                                                       |
| `streams`                | `list[SimulationStream]` | Output streams from the simulation                                                             |
| `estimated_wait_minutes` | `float \| None`          | Estimated wait time in minutes                                                                 |

### SimulationJobsList

Paginated list of simulation jobs.

```python theme={null}
@dataclass(frozen=True, slots=True)
class SimulationJobsList:
    jobs: list[SimulationJobInfo]     # List of simulation job summaries
    total: int                        # Total jobs available
    limit: int                        # Limit used in request
    offset: int                       # Offset used in request
```

| Property | Type                      | Description                      |
| -------- | ------------------------- | -------------------------------- |
| `jobs`   | `list[SimulationJobInfo]` | List of simulation job summaries |
| `total`  | `int`                     | Total jobs available             |
| `limit`  | `int`                     | Limit used in request            |
| `offset` | `int`                     | Offset used in request           |

## Client Credentials Types

<Info>
  Client credentials types were added in v1.3.0
</Info>

### StreamerCapabilities

Capabilities advertised by the connected streamer session.
Used to determine what features are available (e.g., image-to-video).

```python theme={null}
@dataclass(frozen=True, slots=True)
class StreamerCapabilities:
    image_to_video: bool = False

    def to_dict(self) -> dict[str, bool]
    @classmethod
    def from_dict(cls, data: dict) -> StreamerCapabilities
```

| Property         | Type   | Default | Description                                             |
| ---------------- | ------ | ------- | ------------------------------------------------------- |
| `image_to_video` | `bool` | `False` | Whether the streamer supports image-to-video generation |

### ClientCredentials

Pre-minted credentials for client-side connections without an API key.
Created server-side via `create_client_credentials()` and passed to the client application.

```python theme={null}
@dataclass(frozen=True, slots=True)
class ClientCredentials:
    signaling_url: str
    session_token: str
    expires_in: int
    capabilities: StreamerCapabilities | None = None
    session_id: str  # auto-derived from JWT, not user-provided

    def to_dict(self) -> dict[str, Any]
    @classmethod
    def from_dict(cls, data: dict) -> ClientCredentials
```

| Property        | Type                           | Description                                            |
| --------------- | ------------------------------ | ------------------------------------------------------ |
| `signaling_url` | `str`                          | WebSocket URL for the signaling server                 |
| `session_token` | `str`                          | Short-lived JWT for session authentication             |
| `expires_in`    | `int`                          | Token lifetime in seconds                              |
| `capabilities`  | `StreamerCapabilities \| None` | Streamer capabilities (e.g., image-to-video support)   |
| `session_id`    | `str`                          | Derived from the JWT payload — do not provide manually |

**Serialization:**

```python theme={null}
# Server-side: mint and serialize
creds = await server.create_client_credentials()
creds_dict = creds.to_dict()
# Send creds_dict as JSON to the client

# Client-side: deserialize
from odyssey import ClientCredentials
creds = ClientCredentials.from_dict(data_from_server)
```

<Note>
  The `session_id` in the dict is informational.
  On deserialization, it is re-derived from the JWT to prevent tampering.
</Note>

## Broadcast Types

### BroadcastInfo

Broadcast playback URLs for spectators, received via the `on_broadcast_ready` callback when `broadcast=True` is passed to `start_stream()`.

```python theme={null}
@dataclass(frozen=True, slots=True)
class BroadcastInfo:
   webrtc_url: str
   spectator_token: str
   hls_url: str | None = None
```

| Property          | Type          | Description                                            |
| :---------------- | :------------ | :----------------------------------------------------- |
| `webrtc_url`      | `str`         | WebRTC (WHEP) endpoint URL for spectator playback.     |
| `spectator_token` | `str`         | Token required to authenticate a spectator connection. |
| `hls_url`         | `str \| None` | Optional HLS playback URL, if available.               |

### SpectatorConnection

Handle for an active spectator connection. Returned by `connect_to_stream()`.

```python theme={null}
@dataclass(frozen=True, slots=True)
class SpectatorConnection:
   @property
   def is_connected(self) -> bool
   @property
   def peer_connection(self) -> object | None
   async def disconnect(self) -> None
```

| Property          | Type             | Description                                                                                         |
| :---------------- | :--------------- | --------------------------------------------------------------------------------------------------- |
| `is_connected`    | `bool`           | Indicates whether the spectator connection is currently active.                                     |
| `peer_connection` | `Object \| None` | The underlying RTCPeerConnection. Can be used to retrieve WebRTC statistics (e.g., via getStats()). |

| Method       | Description                                                          |
| ------------ | -------------------------------------------------------------------- |
| `disconnect` | Asynchronously close the spectator connection and release resources. |

```python theme={null}
from odyssey import connect_to_stream

def handle_frame(frame):
   cv2.imshow("Broadcast", frame.data)
   cv2.waitKey(1)

connection = await connect_to_stream(
   webrtc_url="http://localhost:8889/live/stream123",
   spectator_token="spectator_abc...",
   on_video_frame=handle_frame,
)

# Later
await connection.disconnect()
```

## Status Types

### ConnectionStatus

```python theme={null}
class ConnectionStatus(str, Enum):
    AUTHENTICATING = "authenticating"  # Authenticating with Odyssey API
    CONNECTING = "connecting"          # Connecting to signaling server
    RECONNECTING = "reconnecting"      # Reconnecting after disconnect
    CONNECTED = "connected"            # Connected and ready
    DISCONNECTED = "disconnected"      # Disconnected (clean)
    FAILED = "failed"                  # Connection failed (fatal)
```

## Configuration Types

### ClientConfig

Configuration for the Odyssey client.

```python theme={null}
@dataclass
class ClientConfig:
    api_key: str                        # API key for authentication (required)
    api_url: str = "https://api.odyssey.ml"  # API URL
    debug: bool = False                 # Enable debug logging
    advanced: AdvancedConfig = AdvancedConfig()  # Advanced settings
```

### AdvancedConfig

Advanced connection settings.

```python theme={null}
@dataclass
class AdvancedConfig:
    max_retries: int = 5              # Max retry attempts
    initial_retry_delay_ms: int = 1000  # Initial retry delay
    max_retry_delay_ms: int = 2000    # Max retry delay
    retry_backoff_multiplier: float = 2.0  # Backoff multiplier
    queue_timeout_s: int = 30         # Queue timeout in seconds
```

## Exceptions

### OdysseyError

Base exception for all Odyssey errors.

```python theme={null}
class OdysseyError(Exception):
    pass
```

### OdysseyAuthError

Raised when authentication fails.

```python theme={null}
class OdysseyAuthError(OdysseyError):
    pass
```

### OdysseyConnectionError

Raised when connection fails.

```python theme={null}
class OdysseyConnectionError(OdysseyError):
    pass
```

### OdysseyStreamError

Raised when a stream operation fails.

```python theme={null}
class OdysseyStreamError(OdysseyError):
    pass
```

## Error Handling

### Fatal vs Non-Fatal Errors

The `on_error` handler receives a `fatal` boolean parameter:

| Fatal   | Description                | Action Required          |
| ------- | -------------------------- | ------------------------ |
| `True`  | Connection cannot continue | Reconnect or exit        |
| `False` | Recoverable error          | May retry or notify user |

### Common Errors

| Error                                                             | Description                             |
| ----------------------------------------------------------------- | --------------------------------------- |
| `OdysseyAuthError`                                                | API key is invalid or expired           |
| `OdysseyConnectionError: No streamers available`                  | No streamers available, try again later |
| `OdysseyConnectionError: Timed out waiting for a streamer`        | Queue timeout expired                   |
| `OdysseyStreamError: Cannot start stream: client is disconnected` | Attempted operation while disconnected  |

## Environment Variables

| Variable          | Description                        |
| ----------------- | ---------------------------------- |
| `ODYSSEY_API_URL` | Override default API URL           |
| `ODYSSEY_API_KEY` | Default API key (used by examples) |
