lanyard.idler.me
A self-hosted Lanyard instance serving one Discord presence over REST and WebSocket.
checking…
This instance requires a key. Paste one to see the live demo — it stays in this page and is never stored.
raw response
—
Endpoints
| GET /v1/users/:id | Presence as JSON key |
| GET /:id.png | Avatar image — also .gif .webp .jpg key |
| WS /socket | Live 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
| Client | Limit |
|---|---|
| keyed | 600 requests / minute per IP |
| tailnet | 6000 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.