Files
cheechandcharliesnortan8bal…/docs/usage.md

5.7 KiB

Usage guide

Installation

pip install conduit-client

For development:

pip install conduit-client[dev]

Authentication

The Conduit gateway uses API keys in the Authorization: Bearer mega_sk_live_<prefix>_<secret> header. Pass your key to ConduitClient or AsyncConduitClient:

from conduit_client import ConduitClient

client = ConduitClient("mega_sk_live_abc123_xyz")

Async version:

import asyncio
from conduit_client import AsyncConduitClient

async def main() -> None:
    async with AsyncConduitClient("mega_sk_live_abc123_xyz") as client:
        me = await client.gateway.me()
        print(me.scopes)

asyncio.run(main())

You can create new keys programmatically using the gateway client:

from conduit_client.models.common import CreateKeyRequest

new_key = client.gateway.create_key(
    CreateKeyRequest(display_name="production", scopes=["sms:send", "sms:read"])
)
print(new_key.key)  # returned exactly once by the server

Async version:

from conduit_client import AsyncConduitClient
from conduit_client.models.common import CreateKeyRequest

async def main() -> None:
    async with AsyncConduitClient("mega_sk_live_...") as client:
        new_key = await client.gateway.create_key(
            CreateKeyRequest(display_name="production", scopes=["sms:send", "sms:read"])
        )
        print(new_key.key)

asyncio.run(main())

Sync vs async

Both clients expose the same façade subclients:

# Sync
with ConduitClient("mega_sk_live_...") as client:
    me = client.gateway.me()

# Async
async with AsyncConduitClient("mega_sk_live_...") as client:
    me = await client.gateway.me()

The asynchronous client uses httpx.AsyncClient under the hood and must be awaited.

Timeouts and retries

client = ConduitClient(
    "mega_sk_live_...",
    timeout=60.0,
    max_retries=2,
)

Async version:

from conduit_client import AsyncConduitClient

client = AsyncConduitClient(
    "mega_sk_live_...",
    timeout=60.0,
    max_retries=2,
)

Retries are applied only to idempotent methods (GET, HEAD, OPTIONS, PUT, DELETE) and only for status codes 429, 502, 503, and 504. Backoff is exponential (0.5s, 1s, 2s, ...).

Custom httpx options

Any extra keyword arguments are forwarded to the underlying httpx.Client or httpx.AsyncClient:

client = ConduitClient(
    "mega_sk_live_...",
    proxies="http://proxy.example.com:8080",
)

Async version:

from conduit_client import AsyncConduitClient

client = AsyncConduitClient(
    "mega_sk_live_...",
    proxies="http://proxy.example.com:8080",
)

Handling free-form API bodies

Some Conduit endpoints forward extra fields directly to the upstream provider (for example, Proxmox VM/container creation, Transmission session patches, DNS zone modifications). For these, the client accepts a dict[str, Any] or a Pydantic model that allows extra fields:

client.compute.patch_vm(100, {"memory": 4096, "cores": 4})
client.torrents.patch_session({"download-dir": "/mnt/media"})
client.dns.patch_zone("example.com", {"type": "slave"})

Async version:

from conduit_client import AsyncConduitClient

async def main() -> None:
    async with AsyncConduitClient("mega_sk_live_...") as client:
        await client.compute.patch_vm(100, {"memory": 4096, "cores": 4})
        await client.torrents.patch_session({"download-dir": "/mnt/media"})
        await client.dns.patch_zone("example.com", {"type": "slave"})

asyncio.run(main())

Errors

All client errors inherit from ConduitError. HTTP error responses are raised as typed exceptions:

Exception Trigger
AuthenticationError 401 / 403
NotFoundError 404
ValidationError 422
ConflictError 409
RateLimitError 429
ConduitAPIError any other 4xx / 5xx

Each exception exposes status_code and response_body:

from conduit_client import NotFoundError

try:
    client.wiki.get_raw("missing:page")
except NotFoundError as exc:
    print(exc.status_code)
    print(exc.response_body)

Async version:

from conduit_client import AsyncConduitClient, NotFoundError

async def main() -> None:
    async with AsyncConduitClient("mega_sk_live_...") as client:
        try:
            await client.wiki.get_raw("missing:page")
        except NotFoundError as exc:
            print(exc.status_code)
            print(exc.response_body)

asyncio.run(main())

Façade clients

Attribute Scope Description
client.gateway gateway:read, keys:create, etc. Health, keys, audit log
client.sms sms:read, sms:send SMS/MMS send/list/delete
client.media_ingest video:read, video:admin MeTube jobs/subscriptions
client.media_library video:read, video:admin Jellyfin search/sessions
client.torrents video:read, video:admin Transmission management
client.wiki wiki:read, wiki:write DokuWiki read/write
client.compute vm:read, vm:start, vm:stop Proxmox VMs/containers/storage
client.dns dns:read, dns:write, dns:delete BIND9 servers/zones/records

Path parameters

Path parameters such as DokuWiki page IDs and DNS zone names are URL-encoded automatically. Slashes, colons, and other special characters are handled safely.

client.wiki.get_raw("namespace:sub:page")
client.dns.get_zone("example.com")

Async version:

from conduit_client import AsyncConduitClient

async def main() -> None:
    async with AsyncConduitClient("mega_sk_live_...") as client:
        await client.wiki.get_raw("namespace:sub:page")
        await client.dns.get_zone("example.com")

asyncio.run(main())