v1 · Public API

API Reference

Programmatic access to the Relyo platform. Post bot and server stats, check vote cooldowns, and identify callers. All with a single API key.

Authentication

All API requests must include your API key in the Authorization header using the Bearer scheme. API keys are prefixed with relyo_.

http
Authorization: Bearer relyo_your_key_here

Generate an API key in your Dashboard → API Keys. Keys are shown only once on creation; store them securely.

javascript
// Install: npm i node-fetch (or use native fetch in Node 18+)

const API_KEY = 'relyo_your_key_here';
const BASE    = 'https://relyo.doombringerz.com/api';

const res = await fetch(`${BASE}/api-keys/me`, {
  headers: { Authorization: `Bearer ${API_KEY}` },
});

const user = await res.json();
console.log(user.username);
python
import httpx

API_KEY = "relyo_your_key_here"
BASE    = "https://relyo.doombringerz.com/api"

headers = {"Authorization": f"Bearer {API_KEY}"}

r = httpx.get(f"{BASE}/api-keys/me", headers=headers)
r.raise_for_status()
print(r.json()["username"])

Permissions

Each API key is issued with a set of permissions that control which endpoints it may call. You choose the permission set when creating a key.

PermissionWhat it allows
readRead-only access: GET /api-keys/me, vote checks
writeSubmit stats updates for bots and servers you own
voteCast votes on behalf of a verified Discord user (webhooks only)

A key can hold multiple permissions. The minimum viable key for most bots is read + write.

Rate limits

Rate limits are per-key, per-IP, per endpoint. When exceeded the API returns 429 Too Many Requests with a Retry-After header (seconds).

EndpointLimitWindow
GET /api-keys/me60 requests60 s
POST /bots/:id/stats5 requests60 s
POST /servers/:id/stats5 requests60 s
GET /votes/:type/:id/check60 requests60 s

Base URL

http
https://relyo.gg/api

All paths shown below are relative to this base. HTTPS is required; HTTP requests are rejected.

Endpoints

GET/api-keys/me
perm: readrate: 60 / min

Returns the Relyo account that owns the calling API key. Useful for verifying the key is valid and inspecting the account linked to it.

Response

json
{
  "id":        "cuid_of_user",
  "discordId": "785880869712822334",
  "username":  "YourBot",
  "avatar":    "https://cdn.discordapp.com/avatars/.../hash.png",
  "role":      "DEVELOPER",
  "createdAt": "2025-01-01T00:00:00.000Z"
}
javascript
const res = await fetch(`${BASE}/api-keys/me`, {
  headers: { Authorization: `Bearer ${API_KEY}` },
});
// { id, discordId, username, avatar, role, createdAt }
const { username, role } = await res.json();
python
r = httpx.get(
    f"{BASE}/api-keys/me",
    headers={"Authorization": f"Bearer {API_KEY}"},
)
user = r.json()
print(user["username"], user["role"])
POST/bots/:id/stats
perm: writerate: 5 / min

Update server and shard count for a bot you own. All fields are optional; send only what changed. Returns 204 No Content on success.

Request body

FieldTypeRequiredDescription
serverCountinteger ≥ 0NoNumber of guilds the bot is in
shardCountinteger ≥ 0NoNumber of active shards
javascript
// POST bot stats - runs in your bot's ready/guildCreate events
await fetch(`${BASE}/bots/${BOT_ID}/stats`, {
  method : 'POST',
  headers: {
    Authorization : `Bearer ${API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    serverCount: client.guilds.cache.size,
    shardCount : client.ws.shards.size,
  }),
});
// 204 No Content on success
python
import httpx

httpx.post(
    f"{BASE}/bots/{BOT_ID}/stats",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
    json={
        "serverCount": len(bot.guilds),
        "shardCount" : len(bot.shards) if bot.shards else 1,
    },
).raise_for_status()

:id is the bot's internal Relyo ID (visible on your bot's dashboard page URL). Only the bot owner or a team member may post stats.

POST/servers/:id/stats
perm: writerate: 5 / min

Update member and online count for a server listing you own. Returns 204 No Content on success.

Request body

FieldTypeRequiredDescription
memberCountinteger ≥ 0NoTotal member count
onlineCountinteger ≥ 0NoOnline / presence count
javascript
// POST server stats - update member counts
await fetch(`${BASE}/servers/${SERVER_ID}/stats`, {
  method : 'POST',
  headers: {
    Authorization : `Bearer ${API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    memberCount: guild.memberCount,
    onlineCount : guild.approximatePresenceCount,
  }),
});
python
import httpx

httpx.post(
    f"{BASE}/servers/{SERVER_ID}/stats",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
    json={
        "memberCount": guild.member_count,
        "onlineCount" : guild.approximate_presence_count,
    },
).raise_for_status()
GET/votes/:entityType/:entityId/check
perm: readrate: 60 / min

Check whether a Discord user has already voted for a bot or server, and when they can vote next. entityType is 'bot' or 'server'.

Query params

ParamTypeRequiredDescription
discordIdstringYesThe Discord user's snowflake ID

Response

json
{
  "voted":      true,
  "nextVoteAt": "2025-06-15T14:00:00.000Z",
  "streak":     5
}
FieldTypeDescription
votedbooleantrue if cooldown is still active (voted in the last 12h)
nextVoteAtISO 8601 string | nullWhen they can vote again; null if they have never voted
streakintegerConsecutive days voted without a miss
javascript
// Check if a Discord user has voted in the last 12h
const res = await fetch(
  `${BASE}/votes/bot/${BOT_ID}/check?discordId=${DISCORD_USER_ID}`,
  { headers: { Authorization: `Bearer ${API_KEY}` } },
);

const { voted, nextVoteAt, streak } = await res.json();

if (voted) {
  const remaining = new Date(nextVoteAt) - Date.now();
  const hours = Math.floor(remaining / 3_600_000);
  console.log(`Already voted. Next vote in ${hours}h`);
} else {
  console.log(`Has not voted (streak: ${streak})`);
}
python
import httpx
from datetime import datetime, timezone

r = httpx.get(
    f"{BASE}/votes/bot/{BOT_ID}/check",
    params={"discordId": DISCORD_USER_ID},
    headers={"Authorization": f"Bearer {API_KEY}"},
)
data = r.json()

if data["voted"]:
    next_vote = datetime.fromisoformat(data["nextVoteAt"])
    remaining = next_vote - datetime.now(timezone.utc)
    print(f"Already voted. Next vote in: {remaining}")
else:
    print(f"Has not voted (streak: {data['streak']})")

Error codes

All errors follow a consistent JSON shape:

json
{
  "statusCode": 429,
  "message":    "Too Many Requests",
  "error":      "TooManyRequestsException"
}
StatusMeaning
200 OKSuccess with body
204 No ContentSuccess (no body - stats endpoints)
400 Bad RequestInvalid input; check the message field for details
401 UnauthorizedMissing or invalid API key
403 ForbiddenValid key but insufficient permission or ownership check failed
404 Not FoundEntity does not exist or is not approved
409 ConflictDuplicate resource (e.g. bot already listed)
429 Too Many RequestsRate limit exceeded; check Retry-After header
503 Service UnavailableTransient backend error; retry with exponential backoff

Need help or want to report a bug? Email us at relyo@doombringerz.com.