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!"
}
FieldTypeRequiredDescription
contentstringYesMessage 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.

ParameterTypeRequiredDescription
limitqueryNoMax messages to return (1-100, default 50)
cursorqueryNoISO 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:

HeaderDescription
Last-Event-IDOptional. 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

ScopeLimit
Global (all endpoints)100 requests per minute
Send/edit/delete message10 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 CodeMeaning
400Invalid request body or parameters
401Missing or invalid authentication
403Insufficient permissions or robot does not belong to this server
404Resource not found
429Rate limit exceeded
500Internal 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}')