Skip to main content

Official Python SDK for the Lag API

Project description

lagclient

PyPI version Python versions CI License: MIT

The official Python SDK for the Lag API.

lagclient is a hand-written REST client covering the public Lag API: users, friends, DMs, servers, rooms, room messages, events, and image uploads. It does not include the WebSocket protocol or the voice client - those are out of scope for this package.

Ships both a synchronous Client (backed by httpx.Client) and an asynchronous AsyncClient (backed by httpx.AsyncClient), sharing a single Pydantic v2 model layer.

  • Typed response models with autocomplete and validation.
  • Typed exception hierarchy with automatic retries on transient failures.
  • Cursor pagination helpers (sync and async iterators).
  • Multipart image upload from path, bytes, or file-like.
  • Zero required dependencies beyond httpx and pydantic.

Install

pip install lagclient

Requires Python 3.9+.

Quickstart

Synchronous

import os
from lagclient import Client

with Client(token=os.environ["LAG_TOKEN"]) as client:
    who = client.identity()
    print(f"Hello, {who.display_name}")

    for server in client.servers.list():
        print(f"- {server.name} ({server.member_count} members)")

Asynchronous

import asyncio
import os
from lagclient import AsyncClient

async def main() -> None:
    async with AsyncClient(token=os.environ["LAG_TOKEN"]) as client:
        who = await client.identity()
        print(f"Hello, {who.display_name}")

        for server in await client.servers.list():
            print(f"- {server.name}")

asyncio.run(main())

Authentication

The SDK accepts two credential types and auto-detects which one you passed:

  • A Personal Access Token (lag_pat_*) - for scripts, CI, and any code acting on behalf of a real user. Create one in the Lag web app under settings, or via the lag CLI with lag auth login. Sent as Authorization: Bearer <token>.
  • A Robot API key (lag_robot_*) - for bots and integrations that act as their own server-scoped identity. Create one when you create a robot on a server. Sent as Authorization: Robot <key>.

Both types are passed via the same token parameter:

from lagclient import Client

# As a user:
user_client = Client(token="lag_pat_...")

# As a robot:
bot_client = Client(token="lag_robot_abcd1234_...")

The SDK does not implement OAuth flows, refresh tokens, or browser-based login - obtain a token elsewhere and pass it in.

Robots

Robot keys are scoped to a single server and have a fixed permission set. The SDK switches the auth scheme to Robot automatically and routes server-scoped actions to the robot endpoints under the hood, so the same resource methods work for both users and robots:

from lagclient import Client

with Client(token="lag_robot_abcd1234_...") as bot:
    me = bot.identity()         # GET /robots/@me/info
    print(me.display_name, me.permissions)

    # Same API as a user, but routed to /robots/@me/servers/...
    bot.servers.rooms.messages.send(
        me.server_id, "room_id", content="Hello from a robot"
    )

    page = bot.servers.rooms.messages.list(me.server_id, "room_id")
    for msg in page["messages"]:
        ...

Methods supported with a robot key:

  • client.identity()
  • client.servers.rooms.list(server_id)
  • client.servers.members.list(server_id)
  • client.servers.rooms.messages.list(server_id, room_id, ...)
  • client.servers.rooms.messages.send(server_id, room_id, ...)
  • client.servers.rooms.messages.edit(server_id, room_id, message_id, ...)
  • client.servers.rooms.messages.delete(server_id, room_id, message_id)

Methods that have no robot equivalent (e.g. users.me(), friends, DMs, events, image uploads) raise LagInvalidTokenError upfront when called with a robot key, or return 401/403 from the API.

Configuration

client = Client(
    token="lag_pat_...",
    base_url="https://api.trylag.com",  # default
    timeout_seconds=30.0,               # default
    max_retries=2,                       # default
    user_agent="my-app/1.0",             # optional override
    extra_headers={"X-My-Header": "v"}, # optional extra headers
    # http_client=httpx.Client(...)     # bring your own
)

max_retries controls how many times the client retries on a transient failure (5xx, 429, network error). Backoff is exponential with jitter, capped at ~8s. The server's Retry-After header is honored on 429.

Both clients are also usable without a context manager:

client = Client(token="lag_pat_...")
try:
    client.users.me()
finally:
    client.close()

Resources

Every resource hangs off the client instance. The async client has the exact same tree, just with await in front of each call.

Attribute What it covers
client.system /health, /version, /system-status, /config
client.users /users/me, /users/me/avatar, /users/:id, /users/search, Steam helpers
client.friends list, requests, send/accept/decline, remove, block
client.dms conversations + messages with cursor pagination
client.servers servers CRUD, icon upload, leave
client.servers.invites create / list / revoke / preview / join
client.servers.members kick, ban, mute (and lists of active bans/mutes)
client.servers.roles role CRUD and assignment
client.servers.rooms voice rooms
client.servers.rooms.messages room chat: list/send/edit/delete with cursor pagination
client.events server events: list/create/get/update/cancel/RSVP
client.events.guests host-side guest moderation
client.events.templates recurring event templates
client.images multipart upload, metadata, status, delete

Pagination

DM and room message endpoints return {messages, hasMore, nextCursor}. You can walk pages yourself:

cursor = None
while True:
    page = client.dms.list_messages("c1", limit=50, cursor=cursor)
    for msg in page["messages"]:
        handle(msg)
    if not page["hasMore"] or page["nextCursor"] is None:
        break
    cursor = page["nextCursor"]

Or use the built-in iterator helpers:

# Sync
for page in client.dms.iter_messages("c1", limit=50):
    for msg in page.items:
        handle(msg)

# Async
async for page in async_client.dms.iter_messages("c1", limit=50):
    for msg in page.items:
        handle(msg)

The same pattern works for room messages via client.servers.rooms.messages.iter(server_id, room_id).

Image upload

# From a file path:
client.images.upload("./avatar.png", purpose="avatar")

# From raw bytes:
with open("./avatar.png", "rb") as f:
    client.images.upload(
        f.read(),
        purpose="avatar",
        filename="avatar.png",
        content_type="image/png",
    )

# From a file-like:
with open("./cover.jpg", "rb") as f:
    client.images.upload(f, purpose="event_cover", filename="cover.jpg")

The maximum upload size is 25 MiB.

Error handling

Every non-2xx response becomes a typed exception you can catch precisely:

from lagclient import (
    LagAPIError,
    LagAuthError,
    LagPermissionError,
    LagNotFoundError,
    LagConflictError,
    LagRateLimitError,
    LagServerError,
    LagConnectionError,
    LagInvalidTokenError,
)

try:
    client.servers.get("does-not-exist")
except LagNotFoundError:
    print("not there")
except LagRateLimitError as err:
    print(f"slow down - retry after {err.retry_after_seconds}s")
except LagAPIError as err:
    print(f"{err.status}: {err}")

Network failures (DNS, refused, timeouts before any response) are raised as LagConnectionError. Everything else is a subclass of LagAPIError.

Local development

python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest
mypy src/lagclient
ruff check src tests
python -m build   # produces wheel + sdist

Tests use respx to mock httpx transport - no real API is required. Every resource has its own test file in tests/.

Related

  • The Lag API itself - product/apps/api/ in the Lag monorepo.
  • @lag/sdk for TypeScript / Node - sibling package in sdks/node/.
  • The lag CLI in cli/ - also MIT licensed.

License

MIT. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lagclient-0.2.0.tar.gz (31.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lagclient-0.2.0-py3-none-any.whl (36.9 kB view details)

Uploaded Python 3

File details

Details for the file lagclient-0.2.0.tar.gz.

File metadata

  • Download URL: lagclient-0.2.0.tar.gz
  • Upload date:
  • Size: 31.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for lagclient-0.2.0.tar.gz
Algorithm Hash digest
SHA256 85a9b82e7eac38372aa170921b0f4bbd0555cf714fe17f7f37b4e21f62bf2ad9
MD5 36b40aa16daa577310f3bddb14e8eeda
BLAKE2b-256 c56119454ab76530d0fa4a8e6cc2787c227fee5eafaed3614ab60cbc4714c8a8

See more details on using hashes here.

Provenance

The following attestation bundles were made for lagclient-0.2.0.tar.gz:

Publisher: release.yml on lag-app/sdk-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lagclient-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: lagclient-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 36.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for lagclient-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 db33599e51f041918aeb68ac44d7aa7dff74e31d6acc27dd17bb37000d3af8e0
MD5 3c43845efdeeae36d072b37213a55ba5
BLAKE2b-256 87bfb5daba7e898692229e03de5bce389025ea6ed44e82eeaea984d5420b39a0

See more details on using hashes here.

Provenance

The following attestation bundles were made for lagclient-0.2.0-py3-none-any.whl:

Publisher: release.yml on lag-app/sdk-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page