Developer Reference

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).

POST /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.

Bot Integration

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. 1

    Send the user to log in

    https://gatlas.app/castyr/login?bot=1
  2. 2

    User logs in with Castyr, lands on /bot/linked

    Shown an 8-character code, valid for 10 minutes, single use. They send it to your bot (e.g. /link CODE).

  3. 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. 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

GET /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.

GET /api/v1/games/:id

Single game by numeric ID. No auth required.

GET /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.

POST /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

GET /api/v1/platforms and /api/v1/platforms/:id

Attributes: name, slug, description. No auth required.

Genres

GET /api/v1/genres and /api/v1/genres/:id

Attributes: name, slug, description. No auth required.

Users

GET /api/v1/users/:id

Public profile fields: username, avatar_url, bio, role, verified.

POST /api/v1/users

Creates a password-based account (not the Castyr flow). Body: { "user": { "username", "email", "password", "password_confirmation" } }.

RSS Feed

GET /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>