{"id":40080987,"url":"https://github.com/spatialwalk/avatar-sdk-python","last_synced_at":"2026-04-01T18:31:06.844Z","repository":{"id":329374864,"uuid":"1118906929","full_name":"spatialwalk/avatar-sdk-python","owner":"spatialwalk","description":"Python SDK for SpatialReal avatars","archived":false,"fork":false,"pushed_at":"2026-03-19T07:27:47.000Z","size":6499,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-19T07:50:04.864Z","etag":null,"topics":["ai","avatar"],"latest_commit_sha":null,"homepage":"https://docs.spatialreal.ai/guide/introduction","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spatialwalk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-18T13:02:42.000Z","updated_at":"2026-03-19T07:27:16.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/spatialwalk/avatar-sdk-python","commit_stats":null,"previous_names":["spatialwalk/avatar-sdk-python"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/spatialwalk/avatar-sdk-python","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spatialwalk%2Favatar-sdk-python","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spatialwalk%2Favatar-sdk-python/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spatialwalk%2Favatar-sdk-python/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spatialwalk%2Favatar-sdk-python/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spatialwalk","download_url":"https://codeload.github.com/spatialwalk/avatar-sdk-python/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spatialwalk%2Favatar-sdk-python/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290869,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","avatar"],"created_at":"2026-01-19T09:07:25.560Z","updated_at":"2026-04-01T18:31:06.838Z","avatar_url":"https://github.com/spatialwalk.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Avatar SDK Python\n\nA Python SDK for connecting to avatar services via WebSocket, supporting audio streaming and receiving animation frames.\n\n## Installation\n\n```bash\npip install avatarkit\n```\n\nTo enable the built-in PCM-to-Ogg-Opus encoder, install the optional `opus` extra:\n\n```bash\npip install \"avatarkit[opus]\"\n```\n\nThe optional encoder uses `opuslib`, which requires a working `libopus` runtime on the\nhost system.\n\n## Quick Start\n\n```python\nimport asyncio\nfrom datetime import datetime, timedelta, timezone\n\nfrom avatarkit import AudioFormat, new_avatar_session\n\nasync def main():\n    # Create session\n    session = new_avatar_session(\n        api_key=\"your-api-key\",\n        app_id=\"your-app-id\",\n        console_endpoint_url=\"https://console.us-west.spatialwalk.cloud/v1/console\",\n        ingress_endpoint_url=\"wss://api.us-west.spatialwalk.cloud/v2/driveningress\",\n        avatar_id=\"your-avatar-id\",\n        expire_at=datetime.now(timezone.utc) + timedelta(minutes=5),\n        transport_frames=lambda frame, last: print(f\"Received frame: {len(frame)} bytes\"),\n        on_error=lambda err: print(f\"Error: {err}\"),\n        on_close=lambda: print(\"Session closed\")\n    )\n\n    # Initialize and connect\n    await session.init()\n    connection_id = await session.start()\n    print(f\"Connected: {connection_id}\")\n\n    # Send audio\n    audio_data = b\"...\"  # Your PCM or Ogg Opus audio data\n    request_id = await session.send_audio(audio_data, end=True)\n    print(f\"Sent audio: {request_id}\")\n\n    # Wait for frames...\n    await asyncio.sleep(10)\n\n    # Close\n    await session.close()\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\n## Detailed Usage\n\n### Session Configuration\n\nThe SDK provides two ways to configure a session:\n\n#### Option 1: Using `new_avatar_session()` (Recommended)\n\n```python\nfrom avatarkit import AudioFormat, new_avatar_session\n\nsession = new_avatar_session(\n    avatar_id=\"avatar-123\",\n    api_key=\"your-api-key\",\n    app_id=\"your-app-id\",\n    # For web-style auth, set use_query_auth=True to put (appId, sessionKey)\n    # in the websocket URL query params instead of headers.\n    use_query_auth=False,\n    expire_at=datetime.now(timezone.utc) + timedelta(minutes=5),\n    console_endpoint_url=\"https://console.us-west.spatialwalk.cloud/v1/console\",\n    ingress_endpoint_url=\"wss://api.us-west.spatialwalk.cloud/v2/driveningress\",\n    sample_rate=16000,  # Default: 16000 Hz\n    audio_format=AudioFormat.PCM_S16LE,\n    transport_frames=on_frame_received,\n    on_error=on_error,\n    on_close=on_close\n)\n```\n\n#### Option 2: Using Configuration Builder\n\n```python\nfrom avatarkit import SessionConfigBuilder, AvatarSession\n\nconfig = (SessionConfigBuilder()\n    .with_avatar_id(\"avatar-123\")\n    .with_api_key(\"your-api-key\")\n    .with_app_id(\"your-app-id\")\n    .with_console_endpoint_url(\"https://console.us-west.spatialwalk.cloud/v1/console\")\n    .with_ingress_endpoint_url(\"wss://api.us-west.spatialwalk.cloud/v2/driveningress\")\n    .with_expire_at(datetime.now(timezone.utc) + timedelta(minutes=5))\n    .with_transport_frames(on_frame_received)\n    .build())\n\nsession = AvatarSession(config)\n```\n\n### Session Lifecycle\n\n```python\n# 1. Initialize (get session token)\nawait session.init()\n\n# 2. Start WebSocket connection\nconnection_id = await session.start()\n\n# 3. Send audio data\nrequest_id = await session.send_audio(audio_bytes, end=True)\n\n# 4. Receive frames via callback\n# (automatically handled in background)\n\n# 5. Close session\nawait session.close()\n```\n\n### Audio Format\n\nThe SDK supports two session-level input formats:\n\n- `AudioFormat.PCM_S16LE` - mono 16-bit PCM bytes\n- `AudioFormat.OGG_OPUS` - one continuous Ogg Opus stream per request ID\n\n#### PCM input\n\n- Sample Rate: one of `[8000, 16000, 22050, 24000, 32000, 44100, 48000]`\n- Channels: 1 (mono)\n- Bit Depth: 16-bit\n- Format: Raw PCM bytes\n\n```python\nfrom avatarkit import AudioFormat\n\nsession = new_avatar_session(\n    ...,\n    sample_rate=16000,\n    audio_format=AudioFormat.PCM_S16LE,\n)\n\nwith open(\"audio.pcm\", \"rb\") as f:\n    audio_data = f.read()\n\nawait session.send_audio(audio_data, end=True)\n```\n\n#### Ogg Opus input\n\n- Sample Rate: one of `[8000, 12000, 16000, 24000, 48000]`\n- Channels: 1 (mono)\n- Format: Ogg Opus pages/chunks\n- Request contract: each request ID must carry one continuous Ogg Opus stream across one or more `send_audio()` calls, and the final chunk must use `end=True`\n\n```python\nfrom avatarkit import AudioFormat\n\nsession = new_avatar_session(\n    ...,\n    sample_rate=24000,\n    bitrate=32000,\n    audio_format=AudioFormat.OGG_OPUS,\n)\n\nwith open(\"audio.ogg\", \"rb\") as f:\n    while chunk := f.read(4096):\n        await session.send_audio(chunk, end=False)\n\nawait session.send_audio(b\"\", end=True)\n```\n\n#### Built-in PCM to Ogg Opus encoder\n\nIf you want the session to negotiate `AudioFormat.OGG_OPUS` but still provide raw PCM\nbytes to `send_audio()`, enable the optional internal encoder.\n\n```python\nfrom avatarkit import AudioFormat, OggOpusEncoderConfig\n\nencoded_outputs = []\n\nsession = new_avatar_session(\n    ...,\n    sample_rate=24000,\n    bitrate=32000,\n    audio_format=AudioFormat.OGG_OPUS,\n    ogg_opus_encoder=OggOpusEncoderConfig(frame_duration_ms=20),\n    on_encoded_audio=lambda req_id, payload: encoded_outputs.append((req_id, payload)),\n)\n\nwith open(\"audio_24000.pcm\", \"rb\") as f:\n    pcm_audio = f.read()\n\nawait session.send_audio(pcm_audio, end=True)\n```\n\nNotes:\n\n- The internal encoder is optional; if you do not install `avatarkit[opus]`, keep using PCM or provide pre-encoded Ogg Opus bytes yourself.\n- `on_encoded_audio` fires when internal encoding completes for a request and receives `(req_id, encoded_audio_bytes)`.\n- Advanced usage still works: if `audio_format=AudioFormat.OGG_OPUS` and `ogg_opus_encoder` is unset, `send_audio()` forwards your pre-encoded Ogg Opus bytes unchanged.\n\n### LiveKit Egress Mode\n\nWhen configured with `livekit_egress`, audio and animation data are streamed to a LiveKit room via the egress service instead of being returned through the WebSocket connection.\n\n```python\nfrom avatarkit import new_avatar_session, LiveKitEgressConfig\n\nsession = new_avatar_session(\n    avatar_id=\"avatar-123\",\n    api_key=\"your-api-key\",\n    app_id=\"your-app-id\",\n    console_endpoint_url=\"https://console.us-west.spatialwalk.cloud/v1/console\",\n    ingress_endpoint_url=\"wss://api.us-west.spatialwalk.cloud/v2/driveningress\",\n    expire_at=datetime.now(timezone.utc) + timedelta(minutes=5),\n    livekit_egress=LiveKitEgressConfig(\n        url=\"wss://livekit.example.com\",\n        api_token=\"livekit-token\",\n        room_name=\"my-room\",\n        publisher_id=\"avatar-publisher\",\n    ),\n)\n```\n\n`api_key` and `api_secret` remain supported for backward compatibility, but they are\ndeprecated in the Python SDK. Prefer `api_token` for new integrations.\n\nWhen LiveKit egress is enabled:\n- The server streams output to the specified LiveKit room\n- The `transport_frames` callback will not be invoked\n- Audio and animation data are published to the room under the specified publisher ID\n\n#### Interrupt (LiveKit Egress Only)\n\nThe `interrupt()` method sends an interrupt signal to stop current audio processing. This is only available when using LiveKit egress mode.\n\n```python\n# Send audio\nrequest_id = await session.send_audio(audio_data, end=True)\n\n# Later, if you need to interrupt (e.g., user wants to stop playback)\ninterrupted_id = await session.interrupt()\nprint(f\"Interrupted request: {interrupted_id}\")\n```\n\nThe interrupt uses the most recent request ID, even after `end=True` was sent. This allows interrupting requests that have finished sending audio but are still being processed by the server.\n\n### Callbacks\n\n#### Transport Frames Callback\n\nReceives animation frames from the server:\n\n```python\ndef on_frame_received(frame_data: bytes, is_last: bool):\n    print(f\"Received frame: {len(frame_data)} bytes\")\n    if is_last:\n        print(\"This is the last frame\")\n    # Process frame_data (contains serialized Message protobuf)\n```\n\n#### Error Callback\n\nHandles errors from the session:\n\n```python\nfrom avatarkit import AvatarSDKError\n\n\ndef on_error(error: Exception):\n    print(f\"Session error: {error}\")\n\n    if isinstance(error, AvatarSDKError):\n        print(\"  code:\", error.code.value)\n        print(\"  phase:\", error.phase)\n        print(\"  http_status:\", error.http_status)\n        print(\"  server_code:\", error.server_code)\n        print(\"  server_detail:\", error.server_detail)\n```\n\nThe SDK reports structured `AvatarSDKError` instances for token creation failures,\nWebSocket upgrade rejections, handshake failures, runtime `ServerError` messages,\nand unexpected connection drops.\n\n### Error Handling\n\nUse `SessionTokenError` for token creation failures and `AvatarSDKError` for all\nother structured SDK errors:\n\n```python\nfrom avatarkit import AvatarSDKError, SessionTokenError\n\ntry:\n    await session.init()\n    await session.start()\nexcept SessionTokenError as error:\n    print(\"token failed\", error.code.value, error.server_detail)\nexcept AvatarSDKError as error:\n    print(\"sdk error\", error.code.value, error.phase, error.server_detail)\n```\n\n`AvatarSDKError` and `SessionTokenError` expose these fields:\n\n- `code` - Stable SDK error code\n- `message` - Human-readable message\n- `phase` - Failure phase such as `session_token`, `websocket_connect`, `websocket_handshake`, `websocket_runtime`, or `websocket_send`\n- `http_status` - HTTP status for token or WebSocket upgrade rejections\n- `server_code` - Server-provided error code, including runtime protobuf `ServerError.code`\n- `server_title` / `server_detail` - Parsed server error details when available\n- `connection_id` / `req_id` - Server correlation identifiers when available\n- `raw_body` - Raw HTTP rejection body for token or WebSocket upgrade failures\n- `close_code` / `close_reason` - WebSocket close details for unexpected disconnects\n\nCommon `AvatarSDKErrorCode` values:\n\n- `sessionTokenExpired` - Session token expired or unauthorized\n- `sessionTokenInvalid` - Invalid or empty session token\n- `appIDUnrecognized` - App ID is not recognized by the server\n- `appIDMismatch` - Session token belongs to a different app\n- `avatarNotFound` - Avatar does not exist\n- `billingRequired` - Session denied by billing checks\n- `creditsExhausted` - Runtime or connect-time credits exhausted\n- `sessionDurationExceeded` - Billing-enforced session timeout reached\n- `unsupportedSampleRate` - Handshake rejected unsupported audio sample rate\n- `invalidEgressConfig` - LiveKit or Agora egress config is invalid\n- `egressUnavailable` - Egress service is unavailable or not configured\n- `idleTimeout` - Server closed the session after input inactivity\n- `upstreamError` - Internal upstream service failed\n- `protocolError` - Invalid protobuf or unexpected message sequence\n- `connectionFailed` - Transport-level connection failure\n- `connectionClosed` - Unexpected WebSocket close\n- `serverError` - Server-side failure that did not match a more specific mapping\n- `invalidRequest` - Other client-side request validation errors\n- `unknown` - Fallback when the SDK cannot classify the failure\n\n#### Close Callback\n\nCalled when the session closes:\n\n```python\ndef on_close():\n    print(\"Session has been closed\")\n```\n\n## API Reference\n\n### AvatarSession\n\nMain class for managing avatar sessions.\n\n#### Methods\n\n- `async init()` - Initialize session and obtain token\n- `async start() -\u003e str` - Start WebSocket connection, returns connection ID\n- `async send_audio(audio: bytes, end: bool = False) -\u003e str` - Send audio data, returns request ID\n- `async interrupt() -\u003e str` - Interrupt current audio processing (LiveKit egress mode only), returns interrupted request ID\n- `async close()` - Close the session and clean up resources\n- `config -\u003e SessionConfig` - Get session configuration (property)\n\n### SessionConfig\n\nConfiguration dataclass for avatar sessions.\n\n#### Fields\n\n- `avatar_id: str` - Avatar identifier\n- `api_key: str` - API key for authentication\n- `app_id: str` - Application identifier\n- `use_query_auth: bool` - Send websocket auth via query params (web) instead of headers (mobile)\n- `expire_at: datetime` - Session expiration time\n- `sample_rate: int` - Audio sample rate (default: 16000)\n- `bitrate: int` - Audio bitrate (default: 0; PCM typically uses 0)\n- `transport_frames: Callable[[bytes, bool], None]` - Frame callback\n- `on_error: Callable[[Exception], None]` - Error callback\n- `on_close: Callable[[], None]` - Close callback\n- `console_endpoint_url: str` - Console API URL\n- `ingress_endpoint_url: str` - Ingress WebSocket URL\n- `livekit_egress: Optional[LiveKitEgressConfig]` - LiveKit egress configuration\n\n### LiveKitEgressConfig\n\nConfiguration for streaming to a LiveKit room.\n\n#### Fields\n\n- `url: str` - LiveKit server URL (e.g., `wss://livekit.example.com`)\n- `api_key: str` - Deprecated LiveKit API key\n- `api_secret: str` - Deprecated LiveKit API secret\n- `api_token: str` - Preferred pre-generated LiveKit access token\n- `room_name: str` - LiveKit room name to join\n- `publisher_id: str` - Publisher identity in the room\n- `extra_attributes: dict[str, str]` - Extra LiveKit participant attributes\n- `idle_timeout: int` - Idle timeout in seconds (0 uses server defaults)\n\n### SessionConfigBuilder\n\nBuilder for constructing SessionConfig with fluent interface.\n\n#### Methods\n\nAll methods return `self` for chaining:\n\n- `with_avatar_id(avatar_id: str)`\n- `with_api_key(api_key: str)`\n- `with_app_id(app_id: str)`\n- `with_use_query_auth(use_query_auth: bool)`\n- `with_expire_at(expire_at: datetime)`\n- `with_sample_rate(sample_rate: int)`\n- `with_bitrate(bitrate: int)`\n- `with_transport_frames(handler: Callable)`\n- `with_on_error(handler: Callable)`\n- `with_on_close(handler: Callable)`\n- `with_console_endpoint_url(url: str)`\n- `with_ingress_endpoint_url(url: str)`\n- `with_livekit_egress(config: LiveKitEgressConfig)`\n- `build() -\u003e SessionConfig` - Build the configuration\n\n### Utility Functions\n\n- `generate_log_id() -\u003e str` - Generate unique log ID in format \"YYYYMMDDHHMMSS_\\\u003cnanoid\\\u003e\"\n\n### Exceptions\n\n- `AvatarSDKError` - Structured SDK error with stable code and context fields\n- `SessionTokenError` - Subclass of `AvatarSDKError` raised when session token request fails\n\n## Examples\n\nSee the [examples](./examples) directory for complete working examples:\n\n- [single_audio_clip](./examples/single_audio_clip) - Basic usage with a single audio file\n- [http_service](./examples/http_service) - Simple HTTP API that returns PCM audio (by sample rate) and generated animation Message binaries\n\n## Protocol Buffers\n\nThe SDK uses Protocol Buffers for efficient serialization. The proto definitions are in `proto/message.proto`.\n\n### Generating Proto Code\n\nProto code is generated using [buf](https://buf.build):\n\n```bash\ncd proto\nbuf generate\n```\n\nThe generated Python code is placed in `src/avatarkit/proto/generated/`.\n\n### Message Types\n\n- `MESSAGE_CLIENT_CONFIGURE_SESSION` (1) - Client session negotiation parameters\n- `MESSAGE_SERVER_CONFIRM_SESSION` (2) - Server confirms and returns `connection_id`\n- `MESSAGE_CLIENT_AUDIO_INPUT` (3) - Client audio input\n- `MESSAGE_SERVER_ERROR` (4) - Server-side error message\n- `MESSAGE_SERVER_RESPONSE_ANIMATION` (5) - Server animation response (`end` indicates final)\n- `MESSAGE_CLIENT_INTERRUPT` (7) - Client interrupt signal to stop processing\n\n## Development\n\n### Setup\n\n```bash\n# Install uv if not already installed\ncurl -LsSf https://astral.sh/uv/install.sh | sh\n\n# Clone and setup\ngit clone \u003crepository-url\u003e\ncd avatar-sdk-python\nuv sync --all-extras\n```\n\n### Running Tests\n\n```bash\n# Unit tests\nuv run pytest\n```\n\n### End-to-End Tests\n\nThe repository includes opt-in network tests in `tests/test_e2e_errors.py` and\n`tests/test_e2e_request.py`. They are skipped by default and only run when\n`AVATARKIT_RUN_E2E=1` is set.\n\n```bash\nAVATARKIT_RUN_E2E=1 uv run pytest tests/test_e2e_errors.py tests/test_e2e_request.py\n```\n\nAvailable e2e cases:\n\n- invalid WebSocket credentials -\u003e expects `sessionTokenInvalid`\n- valid credentials + missing avatar -\u003e expects `avatarNotFound`\n- valid credentials + real avatar + real audio -\u003e sends a request and waits for the final animation frame\n\nEnvironment variables:\n\n- `AVATARKIT_RUN_E2E=1` - Enables e2e tests\n- `AVATARKIT_E2E_API_KEY` - Required for the real `avatarNotFound` test\n- `AVATARKIT_E2E_APP_ID` - Required for the real `avatarNotFound` test\n- `AVATARKIT_E2E_CONSOLE_ENDPOINT` - Required for the real `avatarNotFound` test\n- `AVATARKIT_E2E_INGRESS_ENDPOINT` - Required for the real `avatarNotFound` test unless you use the default public ingress endpoint for the invalid-token test only\n- `AVATARKIT_E2E_MISSING_AVATAR_ID` - Optional avatar id that should not exist; defaults to `avatarkit-e2e-missing-avatar-404`\n- `AVATARKIT_E2E_AVATAR_ID` - Required for the real request test\n- `AVATARKIT_E2E_AUDIO_FORMAT` - Optional, `pcm_s16le` or `ogg_opus`; defaults to `pcm_s16le`\n- `AVATARKIT_E2E_USE_INTERNAL_OGG_OPUS_ENCODER` - Optional, set to `1` to test the SDK's built-in PCM-to-Ogg-Opus encoder\n- `AVATARKIT_E2E_AUDIO_PATH` - Optional audio file path; defaults to `audio_16000.pcm` for PCM and for Ogg Opus when the internal encoder is enabled, otherwise `audio.ogg`\n- `AVATARKIT_E2E_SAMPLE_RATE` - Optional sample rate; defaults to `16000` for PCM and for Ogg Opus when the internal encoder is enabled, otherwise `24000`\n- `AVATARKIT_E2E_BITRATE` - Optional bitrate; defaults to `32000`\n- `AVATARKIT_E2E_CHUNK_SIZE` - Optional chunk size for streaming Ogg Opus; defaults to `4096`\n- `AVATARKIT_E2E_TIMEOUT_SECONDS` - Optional request timeout; defaults to `45`\n- `AVATARKIT_E2E_LIVEKIT_URL` - Required for the real invalid livekit tokrn test\n\nExample:\n\n```bash\nexport AVATARKIT_RUN_E2E=1\nexport AVATARKIT_E2E_API_KEY=\"your-api-key\"\nexport AVATARKIT_E2E_APP_ID=\"your-app-id\"\nexport AVATARKIT_E2E_CONSOLE_ENDPOINT=\"https://console.us-west.spatialwalk.cloud/v1/console\"\nexport AVATARKIT_E2E_INGRESS_ENDPOINT=\"wss://api.us-west.spatialwalk.cloud/v2/driveningress\"\nexport AVATARKIT_E2E_MISSING_AVATAR_ID=\"avatarkit-e2e-missing-avatar-404\"\nexport AVATARKIT_E2E_AVATAR_ID=\"your-real-avatar-id\"\nexport AVATARKIT_E2E_AUDIO_FORMAT=\"pcm_s16le\"\nexport AVATARKIT_E2E_AUDIO_PATH=\"audio_16000.pcm\"\nexport AVATARKIT_E2E_LIVEKIT_URL=\"wss://livekit.example.com\"\n\nuv run pytest tests/test_e2e_errors.py tests/test_e2e_request.py\n```\n\nTo test the SDK's built-in Ogg Opus encoder with a raw PCM fixture:\n\n```bash\nexport AVATARKIT_RUN_E2E=1\nexport AVATARKIT_E2E_API_KEY=\"your-api-key\"\nexport AVATARKIT_E2E_APP_ID=\"your-app-id\"\nexport AVATARKIT_E2E_CONSOLE_ENDPOINT=\"https://console.us-west.spatialwalk.cloud/v1/console\"\nexport AVATARKIT_E2E_INGRESS_ENDPOINT=\"wss://api.us-west.spatialwalk.cloud/v2/driveningress\"\nexport AVATARKIT_E2E_AVATAR_ID=\"your-real-avatar-id\"\nexport AVATARKIT_E2E_AUDIO_FORMAT=\"ogg_opus\"\nexport AVATARKIT_E2E_USE_INTERNAL_OGG_OPUS_ENCODER=\"1\"\nexport AVATARKIT_E2E_AUDIO_PATH=\"audio_16000.pcm\"\nexport AVATARKIT_E2E_SAMPLE_RATE=\"16000\"\n\nuv run pytest tests/test_e2e_request.py -k send_audio_receives_animation_frames -s\n```\n\nIf the credentialed variables are missing, the invalid-token e2e test still runs, and the\nreal `avatarNotFound` test is skipped automatically.\n\nTo run only the real request smoke test:\n\n```bash\nAVATARKIT_RUN_E2E=1 uv run pytest tests/test_e2e_request.py -k send_audio_receives_animation_frames -s\n```\n\n## License\n\nSee [LICENSE](./LICENSE) for details.\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspatialwalk%2Favatar-sdk-python","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspatialwalk%2Favatar-sdk-python","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspatialwalk%2Favatar-sdk-python/lists"}