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_.
Authorization: Bearer relyo_your_key_hereGenerate an API key in your Dashboard → API Keys. Keys are shown only once on creation; store them securely.
// 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);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.
| Permission | What it allows |
|---|---|
| read | Read-only access: GET /api-keys/me, vote checks |
| write | Submit stats updates for bots and servers you own |
| vote | Cast 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).
| Endpoint | Limit | Window |
|---|---|---|
| GET /api-keys/me | 60 requests | 60 s |
| POST /bots/:id/stats | 5 requests | 60 s |
| POST /servers/:id/stats | 5 requests | 60 s |
| GET /votes/:type/:id/check | 60 requests | 60 s |
Base URL
https://relyo.gg/apiAll paths shown below are relative to this base. HTTPS is required; HTTP requests are rejected.
Endpoints
/api-keys/meReturns the Relyo account that owns the calling API key. Useful for verifying the key is valid and inspecting the account linked to it.
Response
{
"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"
}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();r = httpx.get(
f"{BASE}/api-keys/me",
headers={"Authorization": f"Bearer {API_KEY}"},
)
user = r.json()
print(user["username"], user["role"])/bots/:id/statsUpdate 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
| Field | Type | Required | Description |
|---|---|---|---|
| serverCount | integer ≥ 0 | No | Number of guilds the bot is in |
| shardCount | integer ≥ 0 | No | Number of active shards |
// 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 successimport 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.
/servers/:id/statsUpdate member and online count for a server listing you own. Returns 204 No Content on success.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| memberCount | integer ≥ 0 | No | Total member count |
| onlineCount | integer ≥ 0 | No | Online / presence count |
// 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,
}),
});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()/votes/:entityType/:entityId/checkCheck 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
| Param | Type | Required | Description |
|---|---|---|---|
| discordId | string | Yes | The Discord user's snowflake ID |
Response
{
"voted": true,
"nextVoteAt": "2025-06-15T14:00:00.000Z",
"streak": 5
}| Field | Type | Description |
|---|---|---|
| voted | boolean | true if cooldown is still active (voted in the last 12h) |
| nextVoteAt | ISO 8601 string | null | When they can vote again; null if they have never voted |
| streak | integer | Consecutive days voted without a miss |
// 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})`);
}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:
{
"statusCode": 429,
"message": "Too Many Requests",
"error": "TooManyRequestsException"
}| Status | Meaning |
|---|---|
| 200 OK | Success with body |
| 204 No Content | Success (no body - stats endpoints) |
| 400 Bad Request | Invalid input; check the message field for details |
| 401 Unauthorized | Missing or invalid API key |
| 403 Forbidden | Valid key but insufficient permission or ownership check failed |
| 404 Not Found | Entity does not exist or is not approved |
| 409 Conflict | Duplicate resource (e.g. bot already listed) |
| 429 Too Many Requests | Rate limit exceeded; check Retry-After header |
| 503 Service Unavailable | Transient backend error; retry with exponential backoff |
Need help or want to report a bug? Email us at relyo@doombringerz.com.