npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lagapp/sdk

v0.2.0

Published

Official TypeScript/Node SDK for the Lag API

Downloads

23

Readme

@lagapp/sdk

npm version CI License: MIT Node

The official TypeScript / Node SDK for the Lag API.

@lagapp/sdk is a small, hand-written REST client for the public Lag API. It covers 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.

  • Zero runtime dependencies (uses the global fetch from Node 18+).
  • ESM, ships .d.ts declarations.
  • Typed error hierarchy with automatic retries on transient failures.
  • Cursor pagination with an async-iterable helper for walking pages.

Install

npm install @lagapp/sdk
# or
pnpm add @lagapp/sdk
# or
yarn add @lagapp/sdk

Quickstart

import { LagClient } from '@lagapp/sdk';

const client = new LagClient({
  token: process.env.LAG_TOKEN!, // a PAT (lag_pat_*) or robot key (lag_robot_*)
});

const who = await client.identity();
console.log(`Hello, ${who.displayName}`);

const servers = await client.servers.list();
for (const server of servers) {
  console.log(`- ${server.name} (${server.memberCount} members)`);
}

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 field:

// As a user:
const userClient = new LagClient({ token: 'lag_pat_...' });

// As a robot:
const botClient = new LagClient({ 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:

import { LagClient } from '@lagapp/sdk';

const bot = new LagClient({ token: 'lag_robot_abcd1234_...' });

const me = await bot.identity(); // GET /robots/@me/info
console.log(me.displayName, me.permissions);

// Same API as a user, but routed to /robots/@me/servers/...
await bot.servers.rooms.messages.send(me.serverId!, 'room_id', {
  content: 'Hello from a robot',
});

const page = await bot.servers.rooms.messages.list(me.serverId!, 'room_id');
for (const msg of page.messages) {
  // ...
}

Methods supported with a robot key:

  • client.identity()
  • client.servers.rooms.list(serverId)
  • client.servers.members.list(serverId)
  • client.servers.rooms.messages.list(serverId, roomId, ...)
  • client.servers.rooms.messages.send(serverId, roomId, ...)
  • client.servers.rooms.messages.edit(serverId, roomId, messageId, ...)
  • client.servers.rooms.messages.delete(serverId, roomId, messageId)

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

Configuration

const client = new LagClient({
  token: 'lag_pat_...',
  baseUrl: 'https://api.trylag.com', // default
  timeoutMs: 30_000,                  // default
  maxRetries: 2,                       // default
  userAgent: 'my-app/1.0',             // optional override
  fetch: customFetch,                  // optional fetch implementation
});

maxRetries controls how many times the client will retry 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.

Resources

Every resource hangs off the LagClient instance:

| Property | What it covers | |---|---| | client.system | /health, /version, /system-status, /config | | client.users | /users/me, /users/me/avatar, /users/:id, /users/search, /users/check-username, 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 the corresponding lists) | | 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: boolean, nextCursor: string | null }

You can call them by hand:

let cursor: string | undefined;
do {
  const page = await client.dms.listMessages(convId, { limit: 50, cursor });
  for (const msg of page.messages) handle(msg);
  cursor = page.nextCursor ?? undefined;
} while (cursor);

Or async-iterate them with the built-in walker:

for await (const page of client.dms.iterMessages(convId, { limit: 50 })) {
  for (const msg of page.items) handle(msg);
}

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

Image upload

import { readFileSync } from 'node:fs';
import { LagClient } from '@lagapp/sdk';

const client = new LagClient({ token: process.env.LAG_TOKEN! });

// From a file path:
await client.images.upload('./avatar.png', { purpose: 'avatar' });

// From raw bytes:
const bytes = readFileSync('./avatar.png');
await client.images.upload(bytes, {
  purpose: 'avatar',
  filename: 'avatar.png',
  contentType: 'image/png',
});

// From a Blob (works in browsers and Node 18+):
const blob = new Blob([bytes], { type: 'image/png' });
await client.images.upload(blob, { purpose: 'general' });

The SDK builds the multipart request itself; you do not need to construct FormData. Maximum upload size is 25 MiB.

Error handling

Every non-2xx response becomes a typed error you can match with instanceof:

import {
  LagApiError,
  LagAuthError,
  LagPermissionError,
  LagNotFoundError,
  LagConflictError,
  LagRateLimitError,
  LagServerError,
  LagConnectionError,
  LagInvalidTokenError,
} from '@lagapp/sdk';

try {
  await client.servers.get('does-not-exist');
} catch (err) {
  if (err instanceof LagNotFoundError) {
    // 404
  } else if (err instanceof LagRateLimitError) {
    // 429 - err.retryAfterSeconds may contain the server's hint
  } else if (err instanceof LagApiError) {
    // any other API error
    console.error(err.status, err.message, err.body);
  } else {
    throw err;
  }
}

Network failures (DNS, refused connection, timeouts before any response) throw LagConnectionError. Everything else is a subclass of LagApiError.

Local development

npm install
npm run build       # tsc -> dist/
npm run typecheck
npm test            # node --test via tsx

Tests run against an in-process HTTP server (test/helpers/mock-server.ts) so no real Lag API is required. Each resource has its own test file.

Related

  • The Lag API itself - source under product/apps/api/ in the Lag monorepo.
  • lag-sdk for Python (sibling package in sdks/python/).
  • The lag CLI - cli/ in the Lag monorepo, MIT licensed.

License

MIT. See LICENSE.