Developers
API reference
Build your own systems on a server you manage: pay out coins and XP from your game, read member profiles and activity, sync a shop, keep custom commands in a repo, pull the case log into your own tools, post embeds, feed a live game server panel. Keys are made on your developer dashboard. A key acts as its owner and can only reach servers that owner manages. 37 endpoints, all listed below; the same reference is in the docs.
Public endpoints: no key needed
A listed server's levels, bumps, economy, shop, custom commands, active giveaways and 90 days of join/leave history are readable by anyone, for any server in the directory, with no key and no account. So are the platform and per-server leaderboards, and a community's own website feed once its owner turns it on by talking to the bot. See Wire your website. Copy-paste embeds built on this same API are at /widgets, with step-by-step guides for each game server and widget in Game servers and widgets. Everything under Authentication below is the separate, key-gated surface that reaches a server your own account manages.
A listed server's public data
No key. Any server that is listed in the directory, approved, and not NSFW. An unlisted, unapproved or NSFW server answers 404, the same 404 as a guild id that does not exist, so this surface never confirms a server exists by its error shape alone. Rate limited per IP.
/api/espresso/servers/{server}/levelsno keyThe server's level board, highest XP first.
Query: limit (1 to 100, default 25), page (0-indexed, default 0)
Returns
{ "server": { "id", "name", "icon", "slug" }, "items": [{ "rank", "id", "name", "avatar_url", "level", "xp" }], "page", "limit", "total" }/api/espresso/servers/{server}/bumpsno keyWho bumped the server the most.
Query: limit (1 to 100, default 25), page (0-indexed, default 0)
Returns
{ "server", "items": [{ "rank", "id", "name", "avatar_url", "bumps" }], "page", "limit", "total" }/api/espresso/servers/{server}/economyno keyThe server's coin leaderboard.
Query: limit (1 to 100, default 25), page (0-indexed, default 0)
Returns
{ "server", "items": [{ "rank", "id", "name", "avatar_url", "coins" }], "page", "limit", "total" }/api/espresso/servers/{server}/shopno keyThe shop's catalogue: what is for sale, never who bought it. No purchaser or inventory row is ever read to answer this.
Query: limit (1 to 100, default 25), page (0-indexed, default 0)
Returns
{ "server", "items": [{ "id", "name", "description", "price", "role_reward", "stock" }], "page", "limit", "total" }/api/espresso/servers/{server}/commandsno keyThe server's custom commands: the trigger and a 200 character preview of the response.
Query: limit (1 to 100, default 25), page (0-indexed, default 0)
Returns
{ "server", "items": [{ "name", "trigger", "response_preview" }], "page", "limit", "total" }/api/espresso/servers/{server}/giveawaysno keyActive giveaways with an entry count, never the entrant list. Looking-for-group posts are deliberately not published anywhere on this API: an open LFG post is a member's activity right now, not a standing fact about the server, and nobody who posted one agreed to that.
Query: limit (1 to 100, default 25), page (0-indexed, default 0)
Returns
{ "server", "items": [{ "id", "prize", "ends_at", "entry_count" }], "page", "limit", "total" }/api/espresso/servers/{server}/members/historyno keyJoins and leaves per day for the last 90 days, aggregated. No per-member row is ever returned.
Query: limit (1 to 100, default 25), page (0-indexed, default 0)
Returns
{ "server", "items": [{ "day", "joins", "leaves" }], "page", "limit", "total" }/api/espresso/servers/{server}/eventsno keyThe server's upcoming Discord scheduled events, soonest first. Discord already shows these to anyone in the server, so this publishes nothing new: never a creator id or an interested-member list, only the public count.
Query: limit (1 to 100, default 25), page (0-indexed, default 0)
Returns
{ "server", "items": [{ "id", "name", "description", "starts_at", "ends_at", "location", "cover", "interested", "url" }], "page", "limit", "total" }Leaderboards
No key. The same boards the site's /leaderboards pages and the bot's own leaderboard cards read, platform-wide or scoped to one listed server. Base URL is https://api.dispresso.co/v1 for this group, not /api/espresso. 18+ servers are never on these boards for a keyless caller. include_nsfw=1 only asks for them and is honoured just for the signed-in adult who turned adult content on, through the site itself, so adding it to a request from your own code changes nothing.
/v1/public/leaderboardsno keyOne platform-wide board: messages, voice, invites, bumps, levels, coins, or any of the global_* boards (the zoo included).
Query: kind, page, viewer_id, per_page (10 or 50), q, include_nsfw
Returns
{ "guild", "kind", "kinds", "page", "per_page", "query", "total_pages", "total_rows", "rows": [{ "rank", "user_id", "name", "avatar_url", "value" }], "viewer" }/v1/public/servers/{server}/leaderboardsno keyThe same board, scoped to one listed server, plus that server's own placement on the six server-wide boards.
Query: kind, page, viewer_id, per_page, q, include_nsfw
Returns
{ "guild", "kind", "kinds", "page", "per_page", "query", "total_pages", "total_rows", "rows", "placements", "viewer" }/v1/public/leaderboards/serversno keyEvery server whose board is public, so a caller can list or search across them.
Query: q, limit, include_nsfw
Returns
{ "servers": [{ "id", "name", "slug", "icon_url", "member_count" }] }/v1/public/leaderboards/servers/boardno keyOne server-scoped board (most active, largest, most shots, most bumped, largest economy, growing), full and paginated.
Query: kind, page, per_page, q, include_nsfw
Returns
{ "kind", "kinds", "page", "per_page", "query", "total_pages", "total_rows", "rows": [{ "rank", "guild_id", "slug", "name", "icon_url", "member_count", "nsfw", "listed", "value" }] }/v1/public/leaderboards/overviewno keyThe top 5 of every platform-wide board and every server board, in one call.
Query: viewer_id, guild_id, include_nsfw
Returns
{ "userBoards": [{ "kind", "label", "rows", "viewer" }], "serverBoards": [{ "kind", "label", "rows", "viewer" }] }Community site feed
No key. What a community's own website can pull for its Discord: the message of the day, the latest announcements, upcoming scheduled events, and who is on staff. A server turns this on by talking to the bot; see /docs/wire-your-website. Answers 404 for an unlisted server or one that has turned its feed off, and is cached about a minute so a busy website never turns into a gateway poll.
/api/espresso/servers/{server}/site-feedno keyMOTD, announcements, scheduled events and staff for one server's website.
Returns
{ "guild": { "id", "name", "icon", "banner", "member_count" }, "announcements_channel", "motd", "announcements", "events", "staff": { "roles", "members" }, "fetched_at" }Game servers
No key. The catalog of games Dispresso tracks game servers for, and any game server listing that is listed and approved. An unlisted or pending one answers 404. Player counts and status come from each server's own query protocol (A2S, Epic Online Services, the Minecraft/FiveM/Roblox status APIs), not from Discord.
/api/espresso/game-servers/gamesno keyThe games this catalog covers, for building a picker.
Returns
{ "games": [{ "id", "name", "platform", "blurb", "query", "safe" }] }/api/espresso/game-serversno keyListed, approved servers for one game, highest rank first by default.
Query: game_id (required), q, status, sort, region, limit (1 to 200, default 40), offset
Returns
{ "game_id", "total", "limit", "offset", "sort", "region", "servers": [{ "id", "platform", "game_id", "address", "port", "name", "map", "players", "max_players", "rank_score", "last_query_at", "status", "metadata", "claimed_guild_id", "listing_status", ... }] }/api/espresso/game-servers/{id}no keyOne listed, approved server by its id.
Returns
{ "server": { same shape as the servers array above } }/api/espresso/game-servers/{id}/seeding-boardno keyTop seeders and whether the server is asking for players right now. Only for a server with the seeding suite on and its public board enabled; anything else answers 404. Players are shown by name, never by Steam ID.
Query: days (7, 30 or 90, default 30)
Returns
{ "server": { "id", "game_id", "name", "map", "players", "max_players" }, "seeding": { "open", "need", "low", "high" }, "reward": { "kind", "after_minutes", "hours" } | null, "days", "top": [{ "rank", "name", "seconds", "time", "windows" }] }Authentication
Send the key as a bearer token. Keys start with esp_.
curl https://api.dispresso.co/v1/dev/key \
-H "Authorization: Bearer esp_your_key_here"Scopes
Pick scopes when you create the key. A call without the scope it needs answers 403 and names the scope.
guilds:readthe servers the key can reach, a server's summary, roles, channels and emojisstats:readdaily message, voice, join and leave countsmembers:readmember search, profiles and per member activityeconomy:readbalances, leaderboards, the coin ledger, purchases and inventorieseconomy:writecredit and debit server wallets (metered)levels:writegrant server XP through the leveling ladder (metered)shop:readread shop itemsshop:writeadd, replace and remove shop itemscommands:readread custom commandscommands:writereplace custom commandsmoderation:readwarns, timeouts, kicks, bans and the rest of the case logtickets:readopen and closed ticketsgiveaways:readgiveaways, their entries and drawn winnersinvites:readwho invited whom and the invites leaderboardlfg:readlooking for group posts made from the serverforms:readthe forms a server keeps and every submission sent through their buttonsembeds:readsaved embeds and webhooks from the embed builderembeds:writepost an embed to a channel or webhook (metered)game_servers:readread your game server listingsgame_servers:reportregister listings and report live status (metered)
Limits and pricing
Every key has a rate limit per minute (set when you create it, 120 by default, up to 5,000); over it you get 429. Reads are free. Metered calls (wallet adjustments, XP grants, embed sends and game server reports) come with 10,000 free calls a month per account, then one cent per 100 calls from your developer wallet; a call the wallet cannot cover answers 402 and does nothing.
Conventions
- All paths are under
https://api.dispresso.co/v1. - Ids are Discord snowflakes as strings, in and out. Never send them as JSON numbers.
- Timestamps are ISO 8601 in UTC. Days are YYYY-MM-DD in UTC.
- Roles, channels, emojis and member counts come from the bot's latest snapshot of the server, refreshed whenever the dashboard or the bot syncs it;
refreshed_atsays how old it is. - Lists take limit and, where noted, offset. Nothing returns more than 200 rows per call.
Endpoints
Keys and servers
Check a key, then find the servers it can reach and what each one looks like right now.
/dev/keyCheck a key: who owns it and which scopes it carries.
Scope any
Returns
{ "owner_user_id", "key_id", "scopes": [...] }/dev/guildsEvery server the key's owner manages on the dashboard.
Scope guilds:read
Returns
{ "guilds": [{ "id", "name", "owner" }] }/dev/guilds/{guild_id}One server's summary: name, icon, owner, member and online counts from the bot's latest snapshot, lifetime totals from the members the bot tracks, and the last 7 and 30 days of activity.
Scope guilds:read
Returns
{ "name", "owner_id", "icon_url", "member_count", "human_member_count", "online_count", "snapshot_refreshed_at", "totals": { "tracked_members", "messages", "voice_seconds", "xp", "wallet_coins" }, "last_7_days": { "messages", "voice_seconds", "joins", "leaves" }, "last_30_days": { ... } }/dev/guilds/{guild_id}/activityOne row per day of messages, voice seconds, joins and leaves, oldest first.
Scope stats:read Query: days (1 to 365, default 30)
Returns
{ "days", "since", "entries": [{ "day": "2026-09-21", "messages", "voice_seconds", "joins", "leaves" }] }/dev/guilds/{guild_id}/rolesThe server's roles from the bot's snapshot.
Scope guilds:read
Returns
{ "roles": [{ "id", "name", "color" }], "refreshed_at" }/dev/guilds/{guild_id}/channelsThe server's channels in display order, with the category, who is in voice right now and messages in the last hour where the bot sampled them.
Scope guilds:read
Returns
{ "channels": [{ "id", "name", "type", "category_id", "position", "voice_members_now", "messages_1h" }], "refreshed_at" }/dev/guilds/{guild_id}/emojisThe server's custom emojis with image URLs.
Scope guilds:read
Returns
{ "emojis": [{ "id", "name", "animated", "url" }], "refreshed_at" }Members
The people in a server as the bot knows them: standing, counters and activity.
/dev/guilds/{guild_id}/membersSearch or page through the members the bot has a record for, highest XP first. q is a user id for an exact match or part of a name.
Scope members:read Query: q, limit (1 to 200, default 50), offset
Returns
{ "members": [{ "user_id", "name", "is_bot", "xp", "level", "wallet", "messages_sent", "voice_seconds", "last_message_at" }] }/dev/guilds/{guild_id}/members/{user_id}One member's full profile in this server: XP, level, rank, wallet and Bank, messages, voice, warnings, invites, bumps, daily streak, when the bot first saw them and how many times they joined and left.
Scope members:read
Returns
{ "user_id", "name", "is_bot", "xp", "level", "rank_by_xp", "wallet", "bank", "messages_sent", "voice_seconds", "warnings_count", "invites_count", "bumps_count", "daily_streak", "is_jailed", "last_message_at", "first_seen_at", "join_count", "leave_count", "currency": { "name", "emoji" } }/dev/guilds/{guild_id}/members/{user_id}/activityOne member's messages and voice seconds per day.
Scope members:read Query: days (1 to 365, default 30)
Returns
{ "user_id", "days", "since", "entries": [{ "day", "messages", "voice_seconds" }] }Economy and levels
Read balances and history, pay out coins, and grant XP. Every write goes through the bot, so the ledger, the caps and the level roles stay right.
/dev/guilds/{guild_id}/members/{user_id}/balanceOne member's server wallet, Bank, XP, level and message count.
Scope economy:read
Returns
{ "wallet", "bank", "xp", "level", "messages_sent", "currency": { "name", "emoji" } }/dev/guilds/{guild_id}/economy/adjustCredit (positive) or debit (negative) a member's server wallet. Goes through the bot's ledger, balance cap and economy switch, and shows in their history as dev_api:<reason>.
Scope economy:write (metered)
Body
{ "user_id": "123", "amount": 500, "reason": "quest reward" }Returns
{ "ok": true, "moved": 500, "balance": { "wallet", "bank", ... } } or { "ok": false, "reason": "insufficient_funds" | "economy_disabled" | "limit_exceeded", "detail" }/dev/guilds/{guild_id}/leaderboardThe server's leaderboard by wallet coins, XP or messages. Up to 100 rows.
Scope economy:read Query: by (coins, xp or messages), limit (1 to 100, default 25)
Returns
{ "by", "entries": [{ "rank", "user_id", "value", "level"? }] }/dev/guilds/{guild_id}/members/{user_id}/ledgerOne member's coin history in this server, newest first: every mint, burn, purchase, payment and your own dev_api adjustments.
Scope economy:read Query: limit (1 to 200, default 50)
Returns
{ "entries": [{ "id", "layer", "amount", "balance_after", "reason", "reference_type", "reference_id", "metadata", "created_at" }] }/dev/guilds/{guild_id}/levels/grantGrant server XP to a member. It joins the same buffer a message's XP goes through, so the level, the level roles and the level up announcement all follow on the next flush (within a minute). Positive amounts only.
Scope levels:write (metered)
Body
{ "user_id": "123", "amount": 250, "reason": "boss kill" }Returns
{ "ok": true, "granted": 250, "applies", "standing": { "xp", "level" } } or { "ok": false, "reason": "leveling_disabled", "detail" }Shop
Keep a shop in sync from your own tooling and see what people bought.
/dev/guilds/{guild_id}/shop/itemsEvery item in the server's shop.
Scope shop:read
Returns
{ "items": [{ "key", "label", "price_server", "stock", "extra", "category", ... }] }/dev/guilds/{guild_id}/shop/itemsAdd or replace one item by key. reward "ticket" makes a hand-delivered item: buying it opens a ticket and pings the owner.
Scope shop:write
Body
{ "key": "nitro", "label": "Discord Nitro", "price_server": 5000, "reward": "ticket", "consumable": true }Returns
{ "ok": true, "key": "nitro" }/dev/guilds/{guild_id}/shop/items/{key}Remove one item.
Scope shop:write
Returns
{ "ok": true }/dev/guilds/{guild_id}/shop/purchasesShop purchases from the coin ledger, newest first: who paid, how much, and their balance after. The item itself is on the buyer's inventory.
Scope economy:read Query: limit (1 to 200, default 50), offset
Returns
{ "purchases": [{ "id", "user_id", "item_key", "paid", "balance_after", "metadata", "created_at" }] }/dev/guilds/{guild_id}/members/{user_id}/inventoryWhat one member holds from the shop.
Scope economy:read
Returns
{ "items": [{ "key", "label", "category", "quantity", "updated_at" }] }Custom commands
Keep a server's custom commands in a repo.
/dev/guilds/{guild_id}/custom-commandsThe server's custom commands.
Scope commands:read
Returns
{ "commands": [{ "name", "response", "embed" }] }/dev/guilds/{guild_id}/custom-commandsReplace the whole list. !name and "dispresso name" both answer with the response; {user}, {server}, {channel} fill in.
Scope commands:write
Body
{ "commands": [{ "name": "hype", "response": "LETS GO {user}" }] }Returns
{ "ok": true, "count": 1 }Moderation, tickets, giveaways and invites
The records a server keeps, read straight from the tables the bot writes.
/dev/guilds/{guild_id}/moderation/casesThe case log, newest first: warns, timeouts, kicks, bans, jails and purges, with who did it and why. Filter to one member, one action, or only cases still active.
Scope moderation:read Query: user_id, action (warn, kick, ban, unban, timeout, untimeout, jail, unjail, purge), active=true, limit (1 to 200, default 50), offset
Returns
{ "cases": [{ "id", "user_id", "moderator_id", "action", "reason", "duration_seconds", "expires_at", "active", "created_at" }] }/dev/guilds/{guild_id}/ticketsTickets, open ones first, with who opened and claimed them and the transcript link once closed.
Scope tickets:read Query: status (open or closed), limit (1 to 200, default 50), offset
Returns
{ "tickets": [{ "id", "number", "channel_id", "opener_id", "type_key", "claimed_by", "closed", "closed_by", "closed_at", "transcript_url", "created_at" }] }/dev/guilds/{guild_id}/giveawaysClassic and wheel giveaways, running ones first, with the entry ids and the drawn winners.
Scope giveaways:read Query: status (active or ended), limit (1 to 100, default 25)
Returns
{ "giveaways": [{ "kind", "id", "channel_id", "message_id", "host_id", "prize", "winners", "ends_at", "ended", "required_role_id", "entry_count", "entry_ids", "winner_ids", "created_at" }] }/dev/guilds/{guild_id}/invites/leaderboardWho brought the most people: how many they invited, how many are still here, and how many earned the invite reward.
Scope invites:read Query: limit (1 to 100, default 25)
Returns
{ "entries": [{ "rank", "user_id", "name", "brought", "still_here", "rewarded" }] }/dev/guilds/{guild_id}/members/{user_id}/invitesEveryone one member invited, with the code they used and whether they are still in the server.
Scope invites:read Query: limit (1 to 200, default 50)
Returns
{ "brought", "still_here", "invited": [{ "user_id", "invite_code", "source", "first_joined_at", "left_at", "join_count", "reward_state" }] }/dev/guilds/{guild_id}/lfgLooking for group posts made from this server, open ones by default.
Scope lfg:read Query: status=all to include closed and expired, limit (1 to 100, default 25)
Returns
{ "requests": [{ "id", "author_id", "game_key", "platform", "wanted", "note", "interested", "open", "created_at", "expires_at", "closed_at" }] }Forms
The sticky-button forms a server keeps (an outreach log, an application, a report) and the answers members sent through them. Every submission carries who sent it and when, so a sheet or a bot of your own can track staff activity the way the dashboard calendar does.
/dev/guilds/{guild_id}/formsEvery form in the server with its questions, the channel its button sits in, the roles allowed to press it and how many submissions it has.
Scope forms:read
Returns
{ "forms": [{ "id", "key", "title", "description", "channel_id", "button_label", "allowed_role_ids", "questions": [{ "key", "label", "style", "required", "placeholder" }], "log_channel_id", "enabled", "submissions", "created_at" }] }/dev/guilds/{guild_id}/forms/{key}/submissionsThe submissions of one form by its key, newest first, with the member who sent each and their answers in question order.
Scope forms:read Query: user_id, since (ISO date), limit (1 to 200, default 50), offset
Returns
{ "form_key", "submissions": [{ "id", "user_id", "username", "display_name", "answers": [{ "key", "label", "value" }], "submitted_at" }] }Embeds
The embed builder from your own code: read what the server saved in the dashboard studio and post it.
/dev/guilds/{guild_id}/embedsEvery saved embed, newest first, with the full builder payload.
Scope embeds:read
Returns
{ "embeds": [{ "id", "name", "payload", "created_by", "updated_by", "created_at", "updated_at" }] }/dev/guilds/{guild_id}/embeds/{embed_id}One saved embed.
Scope embeds:read
Returns
{ "embed": { "id", "name", "payload", ... } }/dev/guilds/{guild_id}/embeds/webhooksThe webhooks the server saved in the builder, by id and label. The URL never leaves the server.
Scope embeds:read
Returns
{ "webhooks": [{ "id", "label", "created_at" }] }/dev/guilds/{guild_id}/embeds/sendPost a saved embed (embed_id) or a payload in the builder's own shape to a channel through the bot, or to a saved webhook. Same sender as the dashboard's Send button, so the footer and webhook branding rules of the server's tier apply.
Scope embeds:write (metered)
Body
{ "embed_id": "42", "channel_id": "123" } or { "payload": { "content": "hi", "embed": { "title": "..." } }, "webhook_id": "7" }Returns
{ "ok": true, "message_id", "message_url", "via": "bot" | "webhook" }Game servers
Register your own game server listings and keep them live from your tooling.
/dev/game-serversRegister a game server listing from your own tooling.
Scope game_servers:report (metered)
Body
{ "game_name": "Squad", "name": "EU #1", "address": "1.2.3.4", "port": 27015 }Returns
{ "ok": true, "server_id", "server" }/dev/game-servers/{id}/reportReport live players, map and status for a listing you registered.
Scope game_servers:report (metered)
Body
{ "player_count": 40, "max_players": 100, "map": "Yehorivka" }Returns
{ "ok": true, "usage" }/dev/game-servers/{id}Read a listing you registered.
Scope game_servers:read
Returns
{ "server" }Example: pay out a quest reward
curl -X POST https://api.dispresso.co/v1/dev/guilds/1516834065515286598/economy/adjust \
-H "Authorization: Bearer esp_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "user_id": "651502739653656586", "amount": 250, "reason": "daily quest" }'The member sees it in their history as dev_api:daily quest and !bal agrees with your call the moment it returns.
Example: read a member's profile
curl "https://api.dispresso.co/v1/dev/guilds/1516834065515286598/members/651502739653656586" \
-H "Authorization: Bearer esp_your_key_here"Response
{
"guild_id": "1516834065515286598",
"user_id": "651502739653656586",
"name": "gnikss",
"is_bot": false,
"xp": 48210,
"level": 23,
"rank_by_xp": 4,
"wallet": 1250,
"bank": 9800,
"messages_sent": 6120,
"voice_seconds": 88400,
"warnings_count": 0,
"invites_count": 3,
"bumps_count": 12,
"daily_streak": 9,
"is_jailed": false,
"last_message_at": "2026-09-21T01:12:40.000Z",
"first_seen_at": "2026-02-03T18:04:11.000Z",
"join_count": 1,
"leave_count": 0,
"currency": { "name": "beans", "emoji": "" }
}Example: post a saved embed
curl -X POST https://api.dispresso.co/v1/dev/guilds/1516834065515286598/embeds/send \
-H "Authorization: Bearer esp_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "embed_id": "42", "channel_id": "1516834066001797283" }'Build and save the embed in the dashboard studio, then post it from your own code whenever your system decides to. Pass a payload instead of an embed_id to send one you built on the fly.
Errors
- 401: no key, or a revoked one.
- 403: the key is missing a scope, or its owner does not manage that server.
- 402: the developer wallet cannot cover a metered call.
- 404: no such member, item or embed in that server, or the bot has not seen the server yet.
- 429: over the key's rate limit.
- 503: the bot is unreachable; nothing changed, try again.
Missing something? Ask in the support server.