Metadata-Version: 2.5
Name: qriib-meet
Version: 1.1.0
Summary: Qriib Python SDK for room management and meeting links.
Project-URL: Homepage, https://qriib.dev
Author: Qriib
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: meetings,qriib,rooms,sdk,video,webrtc
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Conferencing
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.25.0
Provides-Extra: dev
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# Qriib Meet — Python integration guide

`qriib-meet` is the Python package a backend uses to create and manage Qriib
rooms, then hand the returned meeting link to the client application that
opens the embedded Qriib meeting.

```python
from qriib_meet import QriibMeetClient
```

It is the server-side counterpart of the `qriib_meet` Flutter package. Every
resource, method, model, and error keeps the same name in `snake_case`, so a
team that knows one SDK can read the other.

## Before you start

`qriib-meet` runs on Python 3.10 or newer. The API client signs management
requests with Qriib credentials, while a meeting is opened on a device with the
fresh `final_link` returned by the Qriib API.

A Python process has no camera, microphone, or screen, so the embedded meeting
UI is not part of this package. Create rooms here, then pass the `final_link`
to your Flutter app, which opens it with `client.meetings.join`.

For a production service, do **not** hard-code organization or project secret
keys in source control. Keep them in a secrets manager or environment
configuration, and never log a `final_link`.

## Installation

```bash
pip install qriib-meet
```

Import only the public package entry point:

```python
from qriib_meet import QriibMeetClient
```

## Five-minute integration

1. Create one `QriibMeetClient` when your service starts.
2. Call a room API such as `create_quick_video_room` or `join_room`.
3. Read the server-returned `final_link` and return it to your client app.
4. The Flutter `qriib_meet` package opens the full-screen meeting from it.

```python
from qriib_meet import (
    QriibApiException,
    QriibMeetClient,
    QriibMeetException,
    QriibRoomMetadata,
)

qriib = QriibMeetClient.with_project_credentials(
    api_key="YOUR_PROJECT_API_KEY",
    secret_key="YOUR_PROJECT_SECRET_KEY",
    base_url="https://api.qriib.dev",
    enable_network_logging=False,
)


def start_call(ticket_id: str) -> dict[str, str | None]:
    try:
        response = qriib.rooms.create_quick_video_room(
            project_id="YOUR_PROJECT_ID",
            client_room_id=f"support-call-{ticket_id}",
            metadata=QriibRoomMetadata(room_title="Support call"),
        )
        final_link = qriib.meetings.link_from_response(response)
        return {"room_id": response.get("room_id"), "final_link": final_link}
    except QriibMeetException as error:
        raise RuntimeError(error.message) from error
    except QriibApiException as error:
        raise RuntimeError(error.message) from error
```

`client_room_id` must be unique in the selected project. Use an ID from your
backend domain, for example an order, appointment, or support-ticket ID, or
generate one with `new_client_room_id()`.

## What it provides

- `QriibMeetClient` — API client that can use project and organization credentials (also aliased as `QriibClient` and `VCloudClient`).
- `client.rooms` — create, schedule, start, join, inspect, and end rooms.
- `client.meetings` — validates the server-issued `final_link` that the client
  application opens.
- `client.branding` — upload and update project branding logos and icons.
- `client.analytics`, `client.projects`, and `client.recordings` — management
  and reporting endpoints.

## Credentials and resource access

The client automatically signs the appropriate requests. You do not create
your own HTTP client, headers, `key`, or `hash-signature` values.

| Resource | Required credentials | Main use |
| --- | --- | --- |
| `client.rooms` | Project | Create, join, schedule, inspect, and end rooms |
| `client.meetings` | None; validates a fresh server `final_link` | Hand the meeting link to the client app |
| `client.branding` | Project | Create and update project branding assets |
| `client.analytics` | Project | Read room analytics |
| `client.recordings` | Project | Read recording data and links |
| `client.projects` | Organization | Create and manage projects |

Use `with_project_credentials` for room-only services. Use `with_credentials`
when the same service also calls organization/project-management operations.

## Create the client once

Create one client for the selected Qriib project and close it with the service
or dependency container that owns it. The client is also a context manager.

```python
from qriib_meet import QriibMeetClient

qriib = QriibMeetClient.with_project_credentials(
    api_key="YOUR_PROJECT_API_KEY",
    secret_key="YOUR_PROJECT_SECRET_KEY",
    base_url="https://api.qriib.dev",
    enable_network_logging=True,
    timeout=30.0,
)

# ... later, on shutdown
qriib.close()

# or
with QriibMeetClient.with_project_credentials(api_key="...", secret_key="...") as qriib:
    ...
```

`enable_network_logging` emits request/response summaries on the `qriib_meet`
logger at `INFO` level. Disable it in production. Never hard-code or log
project credentials, `final_link` values, or decoded meeting tokens.

## Create a quick video room and return its link

Quick rooms return a `final_link`. Pass that complete string unchanged to the
client application; do not decode the token yourself.

```python
from qriib_meet import QriibRoomMetadata, new_client_room_id

response = qriib.rooms.create_quick_video_room(
    project_id="YOUR_PROJECT_ID",
    client_room_id=new_client_room_id(),  # Unique inside the project.
    name="Support call",
    moderator_id="moderator-42",
    max_participants=20,
    empty_timeout=1_000,
    metadata=QriibRoomMetadata(
        room_title="Support call",
        welcome_message="Welcome to the call",
        room_duration=60,
    ),
)

final_link = qriib.meetings.link_from_response(response)
room_id = response.get("room_id")
```

Use `create_quick_audio_room` in the same way for an immediate audio room.

## Join an existing room

`join_room` returns the participant-specific `final_link` used to open the
meeting. The role is sent as `user_metadata.role`.

```python
from qriib_meet import QriibUserInfo

response = qriib.rooms.join_room(
    room_id="ROOM_ID",
    user_info=QriibUserInfo(
        name="Karim",
        role="attendee",
        is_admin=False,
        is_hidden=False,
    ),
)

final_link = qriib.meetings.link_from_response(response)
```

## Scheduled rooms

Creating a scheduled room is not the same as starting a meeting:

```text
create scheduled room -> room_id
start scheduled room -> final_link
open Qriib meeting -> client app opens final_link
```

Create the schedule and store the returned `room_id` in your own backend:

```python
scheduled = qriib.rooms.create_scheduled_video_room(
    project_id="YOUR_PROJECT_ID",
    client_room_id="order-123-scheduled-video",
    start_at="2026-12-31T18:30",  # Local ISO-8601 minute precision.
    max_participants=20,
    empty_timeout=1_000,
    metadata=QriibRoomMetadata(room_title="Scheduled review"),
)

room_id = scheduled["room_id"]
```

When the room is started, return its meeting link:

```python
started = qriib.rooms.start_scheduled_room(room_id, name="Scheduled review")
final_link = qriib.meetings.link_from_response(started)
```

Use `create_scheduled_audio_room` for scheduled audio rooms. If the server does
not return `final_link` from `start_scheduled_room`, call `join_room` with the
started `room_id` and use the link from that join response.

## Other room operations

`client.rooms` currently provides these methods:

| Category | Methods |
| --- | --- |
| Create | `create_quick_audio_room`, `create_quick_video_room`, `create_scheduled_audio_room`, `create_scheduled_video_room` |
| Enter/share | `join_room`, `create_invitation_link` |
| Scheduled | `start_scheduled_room` |
| Inspect | `get_room_status`, `get_active_room_info`, `get_active_rooms_info`, `fetch_past_rooms` |
| End | `end_room` |

All resource methods return `dict[str, Any]`. This deliberately preserves the
server response while API response schemas remain flexible. Read only the
fields your integration needs, such as `room_id` and `final_link`.

`fetch_past_rooms` takes `from_` for the API's `from` field because `from` is a
Python keyword.

## Analytics, projects, and recordings

`client.analytics.get_analytics(room_id=...)` and all `client.recordings`
methods require project credentials. Project management methods require
organization credentials, so initialize the client with both scopes when your
service uses all resources:

```python
from qriib_meet import (
    QriibMeetClient,
    QriibOrganizationCredentials,
    QriibProjectCredentials,
)

qriib = QriibMeetClient.with_credentials(
    organization=QriibOrganizationCredentials(
        api_key="YOUR_ORGANIZATION_API_KEY",
        secret_key="YOUR_ORGANIZATION_SECRET_KEY",
    ),
    project=QriibProjectCredentials(
        api_key="YOUR_PROJECT_API_KEY",
        secret_key="YOUR_PROJECT_SECRET_KEY",
    ),
)

analytics = qriib.analytics.get_analytics(room_id="ROOM_ID")
recordings = qriib.recordings.get_records(project_id="PROJECT_ID")
project = qriib.projects.get_project(project_id="PROJECT_ID")
```

Update a project by supplying at least one documented field. Nested settings
are passed as JSON-shaped collections because their inner schema is API-owned:

```python
qriib.projects.update_project(
    project_id="PROJECT_ID",
    default_project=True,
    webhook_url="https://example.com/qriib-webhook",
    room_features=[{"chat": True}],
    default_lock_settings={"locked": True},
)
```

Change a project's status with `QriibProjectStatus`:

```python
from qriib_meet import QriibProjectStatus

qriib.projects.update_project_status(
    project_id="PROJECT_ID",
    status=QriibProjectStatus.inactive,
)
```

## Meeting behavior

`client.meetings` cannot open a meeting from a Python process. It offers:

| Method | Use |
| --- | --- |
| `validate_final_link(final_link)` | Returns the trimmed link, or raises `QriibMeetException` when it has no meeting token. |
| `link_from_response(response)` | Reads `final_link` from a room response, direct or inside a `data` envelope, and validates it. |

Return the complete fresh `final_link` to the participant's device. Never send
only a decoded token, a room ID, or an invitation URL to the client's
`meetings.join`: it requires the complete link returned by the server for that
participant.

## Errors

Handle SDK errors at the call site and show only safe messages to the user:

```python
try:
    final_link = start_call("123")
except QriibMeetException as error:
    show_message(error.message)
except QriibApiException as error:
    show_message(error.message)
```

`QriibMeetException` covers local validation, including a missing or malformed
meeting link. `QriibSessionException` and `QriibNetworkException` are
meeting-specific subclasses kept for parity with the Flutter package.
`QriibApiException` represents a failed API request and includes the server
response and HTTP status for application-level error handling. Typed
subclasses map common statuses:

| Status | Exception |
| --- | --- |
| 400, 422 | `QriibValidationException` |
| 401, 403 | `QriibAuthenticationException` |
| 404 | `QriibNotFoundException` |
| 429 | `QriibRateLimitException` |
| other | `QriibApiException` |

For API errors, prefer `error.message`; it contains a meaningful server message
when one is available, for example `Client room ID already exists`. Do not show
or log raw request credentials or meeting links.

## Development

```bash
pip install -e ".[dev]"
pytest
ruff check .
mypy src
```
