lanyard.idler.me

A self-hosted Lanyard instance serving one Discord presence over REST and WebSocket.

checking…
raw response
—

Endpoints

GET /v1/users/:idPresence as JSON key
GET /:id.pngAvatar image — also .gif .webp .jpg key
WS /socketLive updates, opcode-based protocol key
GET /This page public

Exactly one user ID is served. Any other ID returns 404, whether or not you have a key.

Authentication

A 64-character key is required. Pass it as a query parameter or a header:

curl "https://lanyard.idler.me/v1/users/1492358509851971646?key=YOUR_KEY"

curl -H "Authorization: Bearer YOUR_KEY" \
     https://lanyard.idler.me/v1/users/1492358509851971646

WebSocket clients can only use ?key= — browsers can't set headers on a handshake. Requests from the tailnet skip the key entirely.

Usage

const { success, data } = await (
  await fetch("https://lanyard.idler.me/v1/users/1492358509851971646?key=YOUR_KEY")
).json();

data.discord_status;       // online | idle | dnd | offline
data.spotify;              // null unless listening_to_spotify
data.activities;           // raw Discord activities
const ws = new WebSocket("wss://lanyard.idler.me/socket?key=YOUR_KEY");
ws.onmessage = ({ data }) => {
  const m = JSON.parse(data);
  if (m.op === 1) {                       // Hello
    setInterval(() => ws.send('{"op":3}'), m.d.heartbeat_interval);
    ws.send(JSON.stringify({ op: 2, d: { subscribe_to_ids: ["1492358509851971646"] } }));
  }
  if (m.op === 0 && (m.t === "INIT_STATE" || m.t === "PRESENCE_UPDATE")) {
    render(m.d);
  }
};

Rate limits

ClientLimit
keyed600 requests / minute per IP
tailnet6000 requests / minute

Exceeding a limit returns 429 with Retry-After. IPv6 clients are grouped by /64. The key is rate limited per IP, so a shared key does not share a budget.