Skip to main content

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.

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.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.0

2 files

Supported by

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