API Reference
This reference covers the endpoints available to robots for interacting with servers. Every endpoint is reachable via raw HTTP, the Node SDK (@lagapp/sdk), and the Python SDK (lagclient).
Authentication
All endpoints require a robot API key:
Authorization: Robot lag_robot_<prefix>_<secret> The API key is issued when a robot is created and can be regenerated from the management portal.
The SDKs auto-detect the lag_robot_* prefix and switch to the Robot auth scheme, so the same constructor accepts either a user PAT or a robot key:
import { LagClient } from '@lagapp/sdk';
const client = new LagClient({ token: process.env.LAG_ROBOT_API_KEY! }); import os
from lagclient import Client
client = Client(token=os.environ['LAG_ROBOT_API_KEY'])Base URL
https://api.trylag.com/robots/@me When using the SDKs you don’t need to think about path prefixes - methods like client.servers.rooms.messages.send(...) are transparently rewritten to /robots/@me/servers/... whenever the configured token is a robot key.
Endpoints
Send Message
POST /robots/@me/servers/:serverId/rooms/:roomId/messages Sends a message to a room as the robot. Requires send_messages permission.
Request body:
{
"content": "Hello from the robot!"
} | Field | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Message text (1-2000 characters) |
SDK:
const message = await client.servers.rooms.messages.send(serverId, roomId, {
content: 'Hello from the robot!',
}); message = client.servers.rooms.messages.send(
server_id,
room_id,
content='Hello from the robot!',
)Response (201):
{
"id": "msg_abc123",
"roomId": "room_xyz",
"userId": null,
"username": "My Bot",
"displayName": "My Bot",
"avatarUrl": null,
"content": "Hello from the robot!",
"createdAt": "2024-01-15T12:30:00.000Z",
"editedAt": null,
"robotId": "robot_abc123"
} Edit Message
PATCH /robots/@me/servers/:serverId/rooms/:roomId/messages/:messageId Edits a message previously sent by the robot. Robots can only edit their own messages. Requires send_messages permission.
Request body:
{
"content": "Updated message content"
} SDK:
const updated = await client.servers.rooms.messages.edit(
serverId,
roomId,
messageId,
{ content: 'Updated message content' },
); updated = client.servers.rooms.messages.edit(
server_id,
room_id,
message_id,
content='Updated message content',
)Response (200):
{
"id": "msg_abc123",
"roomId": "room_xyz",
"content": "Updated message content",
"editedAt": "2024-01-15T12:35:00.000Z"
} Get Robot Info
GET /robots/@me/info Returns the authenticated robot’s own details. No specific permission required.
SDK:
The SDK exposes a unified identity() method that works for both user PATs and robot keys. Inspect kind to branch.
const me = await client.identity();
if (me.kind === 'robot') {
console.log(`Robot ${me.displayName} on server ${me.serverId}`);
console.log(`Permissions: ${me.permissions?.join(', ')}`);
} me = client.identity()
if me.kind == 'robot':
print(f"Robot {me.display_name} on server {me.server_id}")
print(f"Permissions: {', '.join(me.permissions or [])}")Response (200):
{
"id": "robot_abc123",
"name": "My Bot",
"serverId": "srv_abc123",
"avatarUrl": null,
"transportType": "sse",
"subscribedEvents": ["room.message", "robot.mentioned"],
"permissions": ["read_messages", "send_messages"],
"active": true,
"createdAt": "2024-01-15T10:00:00.000Z"
} List Rooms
GET /robots/@me/servers/:serverId/rooms Lists all rooms in the robot’s server. Requires read_rooms permission.
SDK:
const server = await client.servers.get(serverId);
for (const room of server.rooms) {
console.log(`#${room.name} (${room.id})`);
} server = client.servers.get(server_id)
for room in server.rooms:
print(f"#{room.name} ({room.id})")Response (200):
[
{
"id": "room_xyz",
"name": "general",
"createdAt": "2024-01-10T09:00:00.000Z"
}
] List Members
GET /robots/@me/servers/:serverId/members Lists all members of the server. Requires read_members permission.
SDK:
const members = await client.servers.members.list(serverId);
for (const member of members) {
console.log(`${member.displayName ?? member.username} (${member.role})`);
} members = client.servers.members.list(server_id)
for member in members:
print(f"{member.display_name or member.username} ({member.role})")Response (200):
[
{
"userId": "usr_456",
"username": "alice",
"displayName": "Alice",
"avatarUrl": null,
"role": "member",
"joinedAt": "2024-01-12T11:00:00.000Z"
}
] Read Messages
GET /robots/@me/servers/:serverId/rooms/:roomId/messages Reads message history from a room. Requires read_messages permission.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | query | No | Max messages to return (1-100, default 50) |
cursor | query | No | ISO timestamp for pagination |
SDK:
Both SDKs expose a single-page list() and an iterator that walks pagination automatically.
// Single page
const page = await client.servers.rooms.messages.list(serverId, roomId, { limit: 50 });
for (const msg of page.messages) {
console.log(`${msg.username}: ${msg.content}`);
}
// Walk all pages
for await (const batch of client.servers.rooms.messages.iter(serverId, roomId)) {
for (const msg of batch.items) {
console.log(`${msg.username}: ${msg.content}`);
}
} # Single page
page = client.servers.rooms.messages.list(server_id, room_id, limit=50)
for msg in page['messages']:
print(f"{msg.username}: {msg.content}")
# Walk all pages
for batch in client.servers.rooms.messages.iter(server_id, room_id):
for msg in batch.items:
print(f"{msg.username}: {msg.content}")Response (200):
{
"messages": [
{
"id": "msg_789",
"roomId": "room_xyz",
"userId": "usr_456",
"username": "alice",
"content": "Hello @mybot",
"createdAt": "2024-01-15T12:30:00.000Z",
"mentions": [{ "type": "robot", "id": "robot_abc123", "name": "mybot" }]
}
],
"hasMore": false,
"nextCursor": null
} Delete Message
DELETE /robots/@me/servers/:serverId/rooms/:roomId/messages/:messageId Deletes a message previously sent by the robot. Robots can only delete their own messages. Requires send_messages permission.
SDK:
await client.servers.rooms.messages.delete(serverId, roomId, messageId); client.servers.rooms.messages.delete(server_id, room_id, message_id)Response (204): No content.
SSE Event Stream
GET /robots/@me/events/sse Opens a persistent Server-Sent Events connection for receiving events in real time. The server sends a keepalive ping every 30 seconds.
Headers:
| Header | Description |
|---|---|
Last-Event-ID | Optional. Resume from a specific event ID to replay missed events. |
Events are delivered as standard SSE with id, event (the event type), and data (JSON payload) fields.
SDK note: event delivery is not yet wrapped by the SDKs. Use raw HTTP for SSE, long-poll, and webhook receivers - see Examples.
See Transport Types for connection details.
Poll Events
GET /robots/@me/events/poll Retrieves pending events via long-polling. The server holds the connection for up to 30 seconds waiting for new events. Returns immediately if events are already queued.
Response (200):
[
{
"id": "evt_abc123",
"eventType": "room.message",
"payload": { ... },
"createdAt": "2024-01-15T12:30:00.000Z"
}
] Returns an empty array if no events arrive within the timeout window. Up to 50 events are returned per request.
SDK note: as with SSE, event polling is not yet wrapped by the SDKs - use raw HTTP. See Examples for a working long-poll loop.
See Transport Types for polling details.
Rate Limits
| Scope | Limit |
|---|---|
| Global (all endpoints) | 100 requests per minute |
| Send/edit/delete message | 10 requests per 10 seconds |
When rate limited, the API returns a 429 Too Many Requests status with a Retry-After header indicating how many seconds to wait. Both SDKs automatically retry rate-limited requests up to maxRetries times (default 2) and honor the Retry-After header.
Error Responses
All errors follow a consistent format:
{
"error": "Bad Request",
"message": "A description of what went wrong",
"statusCode": 400
} | Status Code | Meaning |
|---|---|
| 400 | Invalid request body or parameters |
| 401 | Missing or invalid authentication |
| 403 | Insufficient permissions or robot does not belong to this server |
| 404 | Resource not found |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
The SDKs map these to a typed error hierarchy you can catch:
import {
LagApiError,
LagAuthError,
LagPermissionError,
LagRateLimitError,
} from '@lagapp/sdk';
try {
await client.servers.rooms.messages.send(serverId, roomId, { content: 'hi' });
} catch (err) {
if (err instanceof LagPermissionError) {
console.error('Robot lacks send_messages permission');
} else if (err instanceof LagRateLimitError) {
console.error(`Rate limited - retry after ${err.retryAfterSeconds}s`);
} else if (err instanceof LagApiError) {
console.error(`API error ${err.statusCode}: ${err.message}`);
}
} from lagclient import (
LagAPIError,
LagAuthError,
LagPermissionError,
LagRateLimitError,
)
try:
client.servers.rooms.messages.send(server_id, room_id, content='hi')
except LagPermissionError:
print('Robot lacks send_messages permission')
except LagRateLimitError as e:
print(f'Rate limited - retry after {e.retry_after_seconds}s')
except LagAPIError as e:
print(f'API error {e.status_code}: {e.message}')