Veyra API documentation

Veyra is a Discord presence and profile API for developers who want supported data for users tracked by the Veyra service. It exposes a REST snapshot endpoint, a WebSocket stream, a status JSON endpoint, and a Discord bot command for viewing registered information.

Tracked users only. Having a Discord ID is not the same as being registered with Veyra. The API returns USER_NOT_TRACKED when the user is not stored in Veyra.

REST snapshot

GET /api/user/:userId returns the tracked user's profile, presence, activities, Spotify state, guild membership data, badges, and timestamps.

WebSocket stream

/socket sends INIT_STATE, PRESENCE_UPDATE, and USER_REMOVED events for subscribed user IDs.

Status endpoint

GET /health/json reports service health, Discord readiness, database state, tracked user count, WebSocket connections, uptime, and timestamp.

How Veyra works

The implementation tracks non-bot members of the configured Veyra guild. The source defines the guild as 1399305230268760186. On startup, Veyra fetches guild members, adds or updates current members, and removes stored users who are no longer members.

1

Join the official Veyra Discord

Users join https://discord.gg/ZTeU5HxznV.

2

Complete server verification

The project prompt identifies server verification as required. The provided source code does not include a verification-channel handler, so verification appears to be handled by Discord server configuration or another system outside this repository.

3

Veyra stores the user

On startup sync or guildMemberAdd, the bot waits 5 seconds, confirms the user is still in the guild, and upserts their profile and current presence into MongoDB.

4

Presence and profile updates continue

presenceUpdate, userUpdate, and guildMemberUpdate update stored data and broadcast WebSocket updates to subscribers.

5

Leaving removes tracking

guildMemberRemove deletes the user document and sends a USER_REMOVED WebSocket event to subscribed clients.

Architecture

DiscordGuild members, presences, profile updates, slash commands
Botdiscord.js client with Guilds, GuildMembers, and GuildPresences intents
DataMongoose User model stored in MongoDB
RESTExpress routes for user snapshots and health JSON
WSws WebSocket server mounted at /socket

About Veyra

Veyra is built and maintained by Lorb (@6lxrb). The official creator website is https://lorbspace.vercel.app.

Official links: Veyra Documentation, Veyra Discord, and Lorb.

Quick start

  1. Join the official Veyra Discord at discord.gg/ZTeU5HxznV.
  2. Complete the server's required verification.
  3. Wait for Veyra to register and track your guild membership. The implementation waits 5 seconds after joining before inserting a new user.
  4. Run /info in the Veyra Discord to view your API URL.
  5. Make a request to GET /api/user/:userId.
First request
curl https://veyra-db3d.onbelmo.uk/api/user/YOUR_DISCORD_USER_ID
const response = await fetch("https://veyra-db3d.onbelmo.uk/api/user/YOUR_DISCORD_USER_ID");
const body = await response.json();

if (!body.success) {
  throw new Error(`${body.error.code}: ${body.error.message}`);
}

console.log(body.data.discord_status);
console.log(body.data.discord_user.global_name || body.data.discord_user.username);
type VeyraResponse<T> =
  | { success: true; data: T }
  | { success: false; error: { code: string; message: string } };

const res = await fetch("https://veyra-db3d.onbelmo.uk/api/user/YOUR_DISCORD_USER_ID");
const body = (await res.json()) as VeyraResponse<{ discord_status: string }>;

if (!body.success) throw new Error(body.error.message);
console.log(body.data.discord_status);
import requests

res = requests.get("https://veyra-db3d.onbelmo.uk/api/user/YOUR_DISCORD_USER_ID", timeout=10)
body = res.json()

if not body["success"]:
    raise RuntimeError(f'{body["error"]["code"]}: {body["error"]["message"]}')

print(body["data"]["discord_status"])

Become a tracked user

Veyra can only return supported data for users registered in its database. Registration is created from guild membership, not from arbitrary Discord IDs.

CaseActual behavior from source
Valid Discord ID, not trackedGET /api/user/:userId returns 404 USER_NOT_TRACKED.
User joins the Veyra guildThe guildMemberAdd handler waits 5 seconds, confirms membership, then upserts the user.
Server startupsyncGuildMembers fetches all guild members, upserts non-bot members, and deletes stale records.
User leavesguildMemberRemove deletes the user's database record and broadcasts USER_REMOVED.
User rejoinsThe user is upserted again after the 5 second join delay if still present.
Verification failsNo verification-failure path exists in the provided source. If the server removes or blocks the user outside this app, Veyra will not keep a tracked record.

/info command

The Veyra Discord bot registers a guild slash command named /info. It can show information for yourself or another selected user.

Command/info user?: Discord user

The command only works in the Veyra guild. If the command is used outside the guild, the bot replies ephemerally: Veyra only works in the Veyra guild.

Displayed itemSourceNotes
Monitored Sincetracked_sinceFormatted as a Discord timestamp and relative time.
Account CreationDiscord snowflake timestampCalculated locally from the user ID.
Badgesdiscord_user.badgesDisplays comma-separated badge names or None.
Veyra API URLBASE_URL + /api/user/:userIdAlso appears as a link button labeled API.
K/V KeysHard-coded command fieldCurrently displays none.
Note (kv.note)Hard-coded command fieldCurrently displays none.

Personal API URL. The source builds this URL from the public base URL and Discord user ID. No expiration, regeneration, authentication, or private token behavior is implemented. Treat it as a public link to public Veyra-tracked data.

Authentication

The public REST API and WebSocket gateway do not require API keys or bearer tokens in the provided implementation. CORS allows any origin for GET and OPTIONS.

The server allows an Authorization header through CORS, but no public route validates it. The internal badge fetcher may use DISCORD_USER_TOKEN server-side; clients never send that token to Veyra.

Rate limits

LimitWindowScopeRoutes
200 requests60 secondsIP address, with Express trust proxy enabled/api/*

When the limit is exceeded, Veyra returns 429 with RATE_LIMITED. The implementation enables standard rate-limit headers and disables legacy headers.

Get user

Returns the tracked Discord user's current presence and supported profile information.

GET/api/user/:userId
ParameterTypeRequiredNullableDescription
userIdstringYesNoDiscord snowflake, validated as 17 to 20 digits.
Example response shape
{
  "success": true,
  "data": {
    "kv": {},
    "discord_user": {
      "id": "123456789012345678",
      "username": "example",
      "global_name": "Example",
      "display_name": "Example",
      "discriminator": "0",
      "avatar": "avatar_hash",
      "avatar_url": "https://cdn.discordapp.com/avatars/...",
      "avatar_decoration_data": null,
      "banner": null,
      "banner_url": null,
      "accent_color": null,
      "public_flags": 4194304,
      "badges": ["ACTIVE_DEVELOPER"],
      "primary_guild": null
    },
    "activities": [],
    "discord_status": "online",
    "active_on_discord_web": false,
    "active_on_discord_desktop": true,
    "active_on_discord_mobile": false,
    "active_on_discord_embedded": false,
    "active_on_discord_vr": false,
    "listening_to_spotify": false,
    "spotify": null,
    "custom_status": null,
    "guild": {
      "id": "1399305230268760186",
      "nickname": null,
      "joined_at": "2026-01-01T00:00:00.000Z"
    },
    "last_updated": "2026-01-01T00:00:00.000Z",
    "tracked_since": "2026-01-01T00:00:00.000Z"
  }
}

Try it

This explorer calls the live public endpoint from your browser. It does not use credentials.

Waiting for a request.
{}

Health JSON

GET /health serves the human status page. GET /health/json returns machine-readable health data.

GET/health/json
FieldTypeDescription
statusstringok when database and Discord are ready, otherwise degraded.
servicestringAlways Veyra.
discordbooleanWhether the Discord client has emitted ready.
databasebooleanWhether Mongoose is connected.
badge_apibooleanWhether DISCORD_USER_TOKEN is configured server-side.
uptimenumberProcess uptime in seconds.
tracked_usersnumber | nullUser document count when database is connected.
ws_connectionsnumber | nullCurrent WebSocket client count when available.
timestampstringISO timestamp generated at response time.

WebSocket overview

Connect to wss://veyra-db3d.onbelmo.uk/socket. The server sends a hello payload, then clients subscribe to up to 50 valid Discord snowflakes per initialize message.

Browser WebSocket example
const userId = "YOUR_DISCORD_USER_ID";
let heartbeat;

function connect() {
  const ws = new WebSocket("wss://veyra-db3d.onbelmo.uk/socket");

  ws.addEventListener("message", (event) => {
    const msg = JSON.parse(event.data);

    if (msg.op === 1) {
      ws.send(JSON.stringify({ op: 2, d: { subscribe_to_ids: [userId] } }));
      heartbeat = setInterval(() => {
        if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ op: 3 }));
      }, msg.d.heartbeat_interval);
    }

    if (msg.op === 0 && msg.t === "INIT_STATE") {
      console.log(msg.d[userId] || "User is not tracked");
    }

    if (msg.op === 0 && msg.t === "PRESENCE_UPDATE") {
      console.log(msg.d.discord_status, msg.d.activities);
    }

    if (msg.op === 0 && msg.t === "USER_REMOVED") {
      console.log("Tracking removed for", msg.d.user_id);
    }
  });

  ws.addEventListener("close", () => {
    clearInterval(heartbeat);
    setTimeout(connect, 3000);
  });
}

connect();

WebSocket opcodes

OpNameDirectionPayload
0EventServer to clientContains t and d.
1HelloServer to client{ "heartbeat_interval": 30000 }.
2InitializeClient to server{ "subscribe_to_ids": ["id"] }, filtered to valid snowflakes and sliced to 50 IDs.
3HeartbeatClient to serverNo data required. Sets the client alive flag.
4UnsubscribeClient to server{ "user_ids": ["id"] }.

WebSocket events

EventWhen it is sentData
INIT_STATEAfter op: 2.Object keyed by tracked user ID. Missing IDs are not included.
PRESENCE_UPDATEAfter subscribed user presence, profile, guild display, or badge data changes.Full serialized User object.
USER_REMOVEDWhen a subscribed user leaves the Veyra guild.{ "user_id": "..." }.

Reconnection

The server sends WebSocket pings every 30 seconds and terminates clients that do not respond with pong. Browser clients handle ping and pong automatically. Clients should still send op: 3 heartbeat messages at the interval from op: 1.

Invalid JSON closes the connection with code 1003. The WebSocket server is configured with a 1024 byte max payload, so keep subscription messages compact. Re-send op: 2 after every reconnect because subscriptions live only on the current socket.

User object

FieldTypeNullableDescription
kvobjectNoCurrently serialized as an empty object.
discord_userobjectNoDiscord profile fields: id, username, global_name, display_name, discriminator, avatar, banner, accent color, public flags, badges, decoration data, and primary guild.
activitiesarrayNoNormalized Discord activities from the presence gateway event.
discord_statusstringNoCurrent Discord status or offline.
active_on_discord_webbooleanNoTrue when presence.clientStatus.web exists.
active_on_discord_desktopbooleanNoTrue when presence.clientStatus.desktop exists.
active_on_discord_mobilebooleanNoTrue when presence.clientStatus.mobile exists.
active_on_discord_embeddedbooleanNoTrue when presence.clientStatus.embedded exists.
active_on_discord_vrbooleanNoTrue when presence.clientStatus.vr exists.
listening_to_spotifybooleanNoTrue when a Spotify activity is detected.
spotifyobjectYesCurrent Spotify track object or null.
custom_statusobjectYestext, emoji, emoji_id, emoji_name.
guildobjectNoid, nickname, and joined_at.
last_updatedstringNoDate when Veyra last updated the stored record.
tracked_sincestringNoDate when the Mongo document was first inserted.

Presence

Veyra stores the presence status string provided by Discord. The docs intentionally limit status values to the values handled by the implementation and Discord.js presence objects.

ValueMeaning
onlineUser is online.
idleUser is idle.
dndUser is in Do Not Disturb.
offlineNo presence is available or Veyra defaulted the user to offline.

Activities

Every raw Discord activity is normalized and returned in activities. Veyra also derives top-level spotify and custom_status fields from matching activities.

FieldTypeNullableDescription
idstringYesActivity ID.
namestringYesActivity name.
typenumberYesDiscord activity type: 0 Playing, 1 Streaming, 2 Listening, 3 Watching, 4 Custom Status, 5 Competing.
statestringYesActivity state line.
detailsstringYesActivity detail line.
application_idstringYesDiscord application ID.
timestampsobjectYesstart and end in Unix milliseconds.
assetsobjectYeslarge_image, large_text, small_image, small_text.
partyobjectYesid and size.
sync_idstringYesUsed by Spotify activities.
flagsnumberYesRaw activity flags.
created_atnumberYesActivity creation timestamp in Unix milliseconds.
buttonsstring[]NoButton labels, defaulting to an empty array.

Spotify

Spotify is detected when an activity has name === "Spotify", type === 2, and a syncId. When absent, listening_to_spotify is false and spotify is null.

FieldTypeNullableDescription
track_idstringYesSpotify sync ID from Discord.
timestamps.startnumberYesTrack start time in Unix milliseconds.
timestamps.endnumberYesExpected track end time in Unix milliseconds.
songstringYesTrack name from activity details.
artiststringYesActivity state with semicolons replaced by commas.
albumstringYesActivity large asset text.
album_art_urlstringYesResolved from Discord's Spotify asset URL or spotify: asset hash.

Badges

Badges are returned in discord_user.badges. They are derived from Discord public flags, and when a server-side DISCORD_USER_TOKEN is configured, from Discord profile data such as purchased flags and premium fields.

BadgeSource in code
DISCORD_EMPLOYEEPUBLIC_FLAGS
PARTNERED_SERVER_OWNERPUBLIC_FLAGS
HYPESQUAD_EVENTSPUBLIC_FLAGS
BUG_HUNTER_LEVEL_1PUBLIC_FLAGS
HOUSE_BRAVERYPUBLIC_FLAGS
HOUSE_BRILLIANCEPUBLIC_FLAGS
HOUSE_BALANCEPUBLIC_FLAGS
EARLY_SUPPORTERPUBLIC_FLAGS
BUG_HUNTER_LEVEL_2PUBLIC_FLAGS
VERIFIED_BOT_DEVELOPERPUBLIC_FLAGS
ACTIVE_DEVELOPERPUBLIC_FLAGS
NITRO_CLASSICPURCHASED_FLAGS or premium_type
NITROPURCHASED_FLAGS or premium_type
NITRO_BASICPURCHASED_FLAGS or premium_type
SERVER_BOOSTERpremium_guild_since
NITRO_SUBSCRIBERpremium_since

Implementation note: a separate badge router exists in the repository, but server.js does not mount it. This documentation therefore treats badges as fields on the active user endpoint.

Errors

ErrorStatusMeaningSolution
INVALID_ID400The path user ID is not 17 to 20 digits.Pass a valid Discord snowflake.
BAD_REQUEST400The request path could not be decoded.Use a normal encoded URL path.
FORBIDDEN403The path looked like traversal.Do not request paths containing parent-directory traversal.
USER_NOT_TRACKED404No user document exists for that ID.Join and complete the Veyra Discord process, then retry after tracking starts.
NOT_FOUND404The requested route or protected backend file is not public.Use a documented route.
RATE_LIMITED429The IP exceeded 200 requests in 60 seconds for /api/*.Back off and retry after the window resets.
SERVER_ERROR500An unexpected server error occurred.Retry later or check the status page.

OpenAPI

The rebuilt docs include OpenAPI files for the active REST surface only. Every response includes real, filled-in examples (not just schema), plus per-endpoint auth and rate-limit notes.

Open the interactive API explorer · Download openapi.json or download openapi.yaml.

JavaScript guide

Use direct HTTP requests. Veyra does not include an official SDK in the provided source.

Fetch and render a display name
async function getVeyraUser(userId) {
  const res = await fetch(`https://veyra-db3d.onbelmo.uk/api/user/${userId}`);
  const body = await res.json();
  if (!body.success) throw new Error(body.error.code);
  return body.data;
}

const user = await getVeyraUser("YOUR_DISCORD_USER_ID");
document.querySelector("[data-name]").textContent =
  user.discord_user.global_name || user.discord_user.username;

TypeScript guide

Define local types from the documented response fields. No official TypeScript SDK is present in the repository.

type VeyraError = { success: false; error: { code: string; message: string } };
type VeyraSuccess = { success: true; data: { discord_status: string; listening_to_spotify: boolean } };

async function fetchPresence(userId: string): Promise<VeyraSuccess["data"]> {
  const response = await fetch(`https://veyra-db3d.onbelmo.uk/api/user/${userId}`);
  const body = (await response.json()) as VeyraSuccess | VeyraError;
  if (!body.success) throw new Error(body.error.message);
  return body.data;
}

Python guide

Python applications can use the REST endpoint for snapshots. Use a WebSocket client library when you need live updates.

import requests

def get_veyra_user(user_id: str) -> dict:
    response = requests.get(
        f"https://veyra-db3d.onbelmo.uk/api/user/{user_id}",
        timeout=10,
    )
    body = response.json()
    if not body["success"]:
        raise RuntimeError(body["error"]["code"])
    return body["data"]

React guide

For React or Next.js, fetch Veyra data from a component or your own server route. Do not add private Discord tokens to browser code.

import { useEffect, useState } from "react";

export function VeyraStatus({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`https://veyra-db3d.onbelmo.uk/api/user/${userId}`)
      .then((res) => res.json())
      .then((body) => body.success && setUser(body.data));
  }, [userId]);

  if (!user) return null;
  return <p>{user.discord_user.username}: {user.discord_status}</p>;
}

Migrating from Lanyard

Lanyard conceptVeyra equivalentNotes
/v1/users/:id/api/user/:userIdVeyra uses a different base URL and path.
Discord ID lookupTracked Veyra user lookupVeyra returns USER_NOT_TRACKED when no stored user exists.
Presence over WebSocketwss://veyra-db3d.onbelmo.uk/socketVeyra uses numeric opcodes 1, 2, 3, and 4 plus event opcode 0.
Response fieldsVeyra's serialized User objectMap fields deliberately. Do not assume one-to-one parity.

Troubleshooting

ProblemWhat to check
USER_NOT_TRACKEDConfirm the user joined the official Veyra Discord, completed server verification, and did not leave. Wait for the 5 second join delay or the next startup sync.
Spotify is nullThe user is not currently exposing a Spotify activity matching Veyra's detector.
Presence is not updatingConfirm the Discord bot is ready on /health/json and that the user remains in the Veyra guild.
WebSocket disconnectsReconnect, resubscribe, and keep sending op: 3 heartbeats. Keep messages below 1024 bytes.
HTTP 429Back off. The API limit is 200 requests per 60 seconds per IP.
/info has no API URLThe command returns the API URL only for users found in the database.

FAQ

Can I query any Discord ID?

No. Veyra only returns data for tracked users stored by the service.

Does Veyra require authentication?

No public REST or WebSocket authentication is implemented in the provided source.

Where do I find my API link?

Run /info in the Veyra Discord. The bot displays BASE_URL/api/user/:userId.

Does Veyra have official SDKs?

No SDK package is present in the repository. Use direct REST or WebSocket calls.

What does USER_NOT_TRACKED mean?

Veyra has no stored record for that user. Join and complete the Veyra Discord process so tracking can start.