Game Atlas API
A JSON:API-shaped REST API for reading the Game Atlas catalog, submitting new titles, and an RSS feed for anything that wants to watch for newly published games — including the Discord bot integration.
Base URL & Response Envelope
https://gatlas.app/api/v1
Every JSON response — success or failure — has the same shape:
{
"success": true,
"data": { ... },
"meta": {},
"errors": []
}
Resource objects (games, platforms, genres, users) are serialized JSON:API-style, nested under data.data — { id, type, attributes: {...} }, or an array of those for list endpoints.
Authentication
Write endpoints require a JWT in the Authorization header:
Authorization: Bearer <token>
There are two ways to get a token:
1. Email + password
For accounts created directly via POST /api/v1/users (has a password, not a Castyr-linked account).
/api/v1/auth/login
{ "auth": { "email": "you@example.com", "password": "..." } }
Returns access_token (1 hour) and refresh_token (7 days). Refresh with POST /api/v1/auth/refresh — body { "refresh_token": "..." }.
2. Castyr OAuth (Discord bot / third-party clients)
For accounts that log in with Castyr instead of a password — see Discord Bot Linking below. Same Authorization: Bearer header once you have a token, works on every endpoint the same way.
Discord Bot Linking Flow
A bot can't drive a browser OAuth redirect for a Discord user, so linking is a short-code hand-off — a raw token never has to be pasted into a Discord message.
-
1
Send the user to log in
https://gatlas.app/castyr/login?bot=1 -
2
User logs in with Castyr, lands on
/bot/linkedShown an 8-character code, valid for 10 minutes, single use. They send it to your bot (e.g.
/link CODE). -
3
Bot exchanges the code server-to-server
POST/api/v1/auth/bot_exchange{ "code": "F5KPIDJD" }Response:
{ "success": true, "data": { "access_token": "<jwt, 90 day expiry>", "expires_in": 7776000, "user": { "id": 4, "username": "frostbyteninja" } } } -
4
Store that token per Discord user, use it as the Bearer token on every future request for them
info Codes are single-use and stored server-side, not in a cache — a second exchange attempt with the same code always fails.
Games
/api/v1/games
Paginated, filterable list. Query params: page, per_page (default 20), sort (title|release_date|created_at), order (asc|desc), platform, genre, developer, publisher, release_year. No auth required.
/api/v1/games/:id
Single game by numeric ID. No auth required.
/api/v1/games/search?q=elden
Matches title, slug, developer, and publisher. Use this to check whether a game is already in Game Atlas before submitting it — an empty data.data array means it isn't. No auth required.
/api/v1/games
Auth required
Submits a new game. Always created with status: "pending" — it lands in the admin moderation queue, not live, until an admin reviews and approves it.
{
"game": {
"title": "Hollow Knight: Silksong",
"developer": "Team Cherry",
"publisher": "Team Cherry",
"description": "...",
"release_date": "2025-09-04"
}
}
Only title is required. Returns 201 with the created game, or 401 without a valid token.
Platforms
/api/v1/platforms
and
/api/v1/platforms/:id
Attributes: name, slug, description. No auth required.
Genres
/api/v1/genres
and
/api/v1/genres/:id
Attributes: name, slug, description. No auth required.
Users
/api/v1/users/:id
Public profile fields: username, avatar_url, bio, role, verified.
/api/v1/users
Creates a password-based account (not the Castyr flow). Body: { "user": { "username", "email", "password", "password_confirmation" } }.
RSS Feed
/feed
(aliases: /feed.xml, /games.rss)
Standard RSS 2.0, the 30 most recent games — but unlike a typical feed, it includes both pending and approved games, not just live ones. Check each item's <gameatlas:status> — pending or approved.
Sorted by whichever is more recent per game: published_at (stamped once, the moment it's first approved — editing an already-published game later never bumps it or duplicates it) for approved games, created_at for pending ones.
Each <item> has a stable <guid> (the game's permalink) that doesn't change when a game moves from pending to approved — same item, new status, refreshed <pubDate>. Track status per guid (not just "seen/unseen") if you want your bot to react to both events — e.g. post "new submission" on first sight, then edit that message to "now live" when status flips.
<item>
<title>Elden Ring</title>
<link>https://gatlas.app/games/7</link>
<guid isPermaLink="true">https://gatlas.app/games/7</guid>
<pubDate>Sun, 02 Aug 2026 16:13:39 +0000</pubDate>
<gameatlas:status>approved</gameatlas:status>
<description>...</description>
<enclosure url="https://cdn.gatlas.app/..." type="image/jpeg" length="123456"/>
</item>