Skip to main content

oix: Open WebUI SDK

A Python SDK for building apps for Open WebUI, with authenticated events and a signed API client.

Requires Python 3.11+ and Open WebUI with Apps support.

Install

python -m pip install oix

A minimal app

import os
from pathlib import Path

from oix import App, AppChannelReply, Client, Event

app = App(
    manifest_id="hello",
    name="Hello",
    api_key=os.environ["API_KEY"],
    private_key=Path(os.environ["APP_PRIVATE_KEY_FILE"]).read_text(),
    scopes=["channels:mentions:read"],
)


@app.on("channel.mention")
async def hello(event: Event, client: Client) -> AppChannelReply:
    return AppChannelReply(content="Hello! Only you can see this reply.")


if __name__ == "__main__":
    app.run()

manifest_id identifies your app in its manifest. WebUI assigns a separate app_id to each installation; incoming events and their clients already carry that ID.

Save the example as app.py. Generate an Ed25519 key and an API key, then start the app:

umask 077
openssl genpkey -algorithm ED25519 -out app-private-key.pem
export APP_PRIVATE_KEY_FILE="$PWD/app-private-key.pem"
export API_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')"
python app.py

The app listens on port 8000 at /events. Use app.run(port=8001, path="/manifest") to change these. Press Ctrl+C to stop it.

Keep both secrets in persistent deployment storage and reuse them across restarts.

In WebUI, enable Apps, add your public HTTPS event URL and the API key, approve the scopes, then connect. Invite the app into a channel. GET on the event URL provides the manifest; POST receives authenticated events.

Pass manifest=False to app.run() for a POST-only endpoint and enter the app details manually in WebUI. app.manifest supplies those details.

For localhost development, pass the exact WebUI origin in allowed_private_origins=("http://127.0.0.1:8080",). Configure WebUI's APP_ALLOWED_PRIVATE_ORIGINS to allow your app origin. Production callbacks require HTTPS and public destinations by default.

Events and replies

Register async handlers with @app.on(...) for:

  • channel.mention: explicit app mentions; text is in event.data["text"].
  • channel.action: a saved message button; event.data contains message_id, action_id, and context.
  • channel.operation.status: a status request for deferred work.
  • channel.message.created, channel.message.updated, channel.message.deleted: ordinary channel events, including messages without mentions. Request channels:messages:read for these.

All callbacks receive (event, client). Events are verified against the originating WebUI before your handler runs. For interactions, the actor's admin status reflects their current authorization. Ordinary message events do not authorize commands on behalf of their authors.

Return AppChannelReply(content="...") for an inline private reply, or set visibility="channel" to post publicly in the channel. AppChannelReply also supports update, download, error, and deferred. Action(action_id="...", label="...", context={...}) buttons are supported only on saved channel messages. Button handlers must enforce any operation-specific rules themselves.

Return AppChannelReply(type="deferred", operation_id=event.id) for background work and implement a channel.operation.status handler. Keep status replies private. Your app manages workers, job storage, and restart recovery. Recheck await client.authorize(event.execution_token) before later sensitive work, and apply your app's own permissions.

Ordinary channel-event handlers return None; use the runtime client to send a message. A mention can produce both a channel event and a mention interaction. Check event.message["author"] to avoid responding to your own messages. Delivery is best effort, can arrive out of order, and has no replay guarantee.

Scopes

Apps request scopes in their manifest. Admins choose which to grant and can change that selection in app settings. Channel access also requires an invitation to the channel.

  • channels:mentions:read: receive explicit app mentions, without message history.
  • channels:messages:read: receive message events, read history and thread replies, and receive mentions.
  • channels:messages:write: send messages and edit the app's own messages, including public callback replies.
  • channels:read: list joined channels and their details, without messages.

Write access does not grant read access. Private replies do not require write access.

Runtime client

from oix import AppChannelMessage

message = await client.create_message(
    event.channel["id"], AppChannelMessage(content="Working on it...")
)
await client.update_message(
    message["id"], AppChannelMessage(content="Done."), etag=message["etag"]
)

Available methods: get_me(), list_channels(), list_messages(channel_id, limit=50, before=None, parent_id=None), create_message(...), update_message(...), authorize(execution_token), and verify_event(event_token). Responses are WebUI JSON dictionaries/lists. APIError.status exposes HTTP failures, including 412 for stale edits. Transport failures use aiohttp exceptions or TimeoutError. Message creation is never automatically retried.

One App can serve multiple independent WebUI instances. Each event supplies its WebUI origin and local installation ID; its client is bound to that pair. For proactive messages, save the destination URL and app ID alongside your subscription, then call app.client(webui_url, app_id) after restart. API-key holders are trusted to connect instances; a shared key does not provide tenant-specific business authorization.

Existing web servers

To mount the app in FastAPI, install oix[fastapi] and include its router:

from fastapi import FastAPI
from oix.fastapi import create_router

server = FastAPI()
server.include_router(create_router(app, path="/events"))

Run your existing server instead of calling app.run(). The router also accepts manifest=False.

For another web framework, call app.authenticate(authorization) before reading a body, enforce a 256 KiB body limit, parse JSON, then await app.handle_event(payload, authorization=authorization). Return the resulting JSON.

Development

uv sync --extra fastapi
uv run ruff check .
uv run ruff format --check .
uv run pytest
uv build
uv run twine check dist/*

Metadata

Release files for oix 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for oix 0.3.0
File Size Uploaded
oix-0.3.0.tar.gz 128.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for oix 0.3.0
File Interpreter ABI Platform
oix-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 140.7 kB

Release files / oix-0.3.0.tar.gz

Download URL oix-0.3.0.tar.gz
Size 128.8 kB
Tags Source
SHA-256 checksum
How to use checksums
4b8eed8cc9d253c63aeb46ee146f01e94e0d420162db2e822469d302db8049f4
BLAKE2b-256 checksum
How to use checksums
9c811fc4c63e30eb3a797318cc8d5c3b5d493b1a8d8763b61f6abf5ed0326957
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / oix-0.3.0-py3-none-any.whl

Download URL oix-0.3.0-py3-none-any.whl
Size 11.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cf06c7d9f3de36947d835154728c0ace7133069aeb2a8a317cce69dff295fa98
BLAKE2b-256 checksum
How to use checksums
02605f58e98a1c444327c6392de891c27600fe15cdbed4eeb764ca8ae2dccb0b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page