REST API overview
The Cool GPT Games REST API is the source of truth for everything you do as a developer: creating games, uploading builds, reading your stats, running tournaments, configuring live-ops and webhooks. The Publisher SDK, the CLI and the MCP server are thin wrappers over the endpoints on this page.
This page covers the conventions that apply to every endpoint, then gives an index of every developer-facing endpoint with links to the page that documents it in detail.
Base URL
https://api.coolgptgames.comEvery path in these docs is relative to that origin, for example GET https://api.coolgptgames.com/v1/me/games.
All endpoints live under the /v1/ prefix. Two unversioned health endpoints also exist (GET /health, GET /ready); they are for uptime monitoring and carry no data you need.
Authentication
There are two ways to authenticate. Both identify your account, and both are checked on every request.
Method | How it's sent | Who uses it | Scopes |
|---|---|---|---|
API key |
| Your servers, scripts, CI, the CLI, the Publisher SDK, the MCP server | Limited to the scopes chosen when the key was created |
Interactive session | Set by signing in on coolgptgames.com (browser cookie, or a session bearer token) | The website and dashboard | Full access to your account; never scope-limited |
API keys
Create keys in the dashboard under Dashboard → API keys (see API keys & scopes). Send the key as a bearer token:
curl https://api.coolgptgames.com/v1/me/games \
-H "Authorization: Bearer $COOLGPTGAMES_API_KEY"New keys start with
ak_. Keys created before September 2026 start witharc_live_and keep working unchanged. Treat both the same way.API keys are accepted only in the
Authorization: Bearerheader. A key placed in a cookie is ignored.A revoked key stops working immediately, on the very next request.
A key belonging to a banned or deleted account stops working immediately.
Key requests are exempt from CSRF checks (they can't ride an ambient browser cookie), so no extra headers are needed.
What API keys can never do
Some routes manage your account itself (its email, its keys, its payout details, its public profile) or spend and move coins. Those routes require an interactive session and reject any API key, whatever its scopes, with:
{
"error": {
"code": "session_required",
"message": "API keys can't manage your account — sign in to the dashboard."
}
}(HTTP 403.) This means a leaked key, even a full-access one, can't mint new keys, revoke your other keys, change your email, delete your account, redirect your payouts, spend your coins, play ranked on someone's behalf, or swear a legal statement in your name. The session-only routes are:
Method & path | What it does |
|---|---|
| Create an API key |
| List your API keys (each with |
| Revoke an API key |
| Start payout (bank) onboarding |
| Edit your public profile |
| Upload your avatar |
| Upload your profile banner |
| Change your account email |
| Delete your account |
| Notification preferences |
| Onboarding interests |
| Ad-personalisation age declaration |
| Claim the daily coin reward |
| Gift coins to a friend |
| Buy from the platform cosmetics shop |
| Claim a season reward tier |
| Buy an in-game store item |
| Spend units of a consumable the player owns |
| Fund a tournament prize pool (free tournaments still accept a key) |
| Every per-player ranked route. |
| Your DMCA-removed games and their notices |
| File a DMCA counter-notice (a sworn statement) |
Do these signed in on coolgptgames.com.
Calling from a browser
The API only accepts cross-origin browser requests from Cool GPT Games' own sites. Call the API from your server, script or CI job, not from JavaScript running on your own website. It's also the right choice for security: an API key in browser code is a leaked key.
Inside a game you never call the REST API directly. Games talk to the platform through the GameSDK (see GameSDK core reference), and the player page makes the API calls for you.
Session cookies and CSRF
This only matters if you are building against the API with a browser session rather than a key. Cookie-authenticated POST, PUT, PATCH and DELETE requests must echo the value of the csrf cookie in an X-CSRF-Token header, or they fail with 403 forbidden ("CSRF token missing or invalid"). Bearer-token requests (API keys and session tokens) are exempt.
Roles
Every account can both play and publish games. Creator endpoints check that the caller owns the game in question. Acting on someone else's game returns 403 forbidden (or 404 not_found where the game's existence shouldn't be revealed).
Requests
JSON bodies. Send
Content-Type: application/jsonwhen there is a body. Don't send that header on requests without a body (for exampleDELETE /v1/games/:id), because an empty body declared as JSON is rejected.Binary bodies. A few endpoints take raw bytes instead of JSON:
Bundle uploads to the presigned
uploadUrl:Content-Type: application/zip.Game images (
POST /v1/games/:id/media):image/png,image/jpeg,image/giforimage/webp. The real type is detected from the file's bytes, not the header.
Body size. Every endpoint accepts at most 1 MB of body unless documented otherwise. Image uploads allow 5 MB. Bundles go straight to storage through the presigned URL, with a limit that depends on the runtime. See Errors & limits. - IDs vs slugs. Write and owner endpoints take the game's id (
:id). Public read endpoints take its slug (:slug).POST /v1/gamesreturns both.GET /v1/me/gameslists both for all your games.Timestamps are ISO 8601 strings in UTC. Date-only fields (such as daily earnings) are
YYYY-MM-DD.Unknown fields in a JSON body are ignored.
Responses
Successful responses are JSON objects, usually 200 OK. Creation endpoints return 201 Created (POST /v1/games, POST /v1/games/:id/versions, POST /v1/me/keys, POST /v1/dev/webhooks). Most action endpoints that have nothing else to return send { "ok": true }.
Money amounts in creator earnings responses are US dollars, in fields ending Usd. Coins (fields ending Coins, or named coins) are the platform's virtual currency. They are not real money and can't be cashed out.
Errors
Every error handled by the API uses the same envelope:
{
"error": {
"code": "cover_required",
"message": "Add a cover image before publishing your game.",
"details": {}
}
}Field | Meaning |
|---|---|
| Stable, machine-readable string. Branch on this. |
| Human-readable explanation. Can change wording at any time, so don't parse it. |
| Optional. Present on some errors, notably |
The HTTP status tells you the class of the error (400 bad input, 401 not authenticated, 403 not allowed, 404 not found, 409 conflict, 429 rate limited, 5xx server-side). Every response uses this envelope, including a request to a path that doesn't exist at all: that returns 404 with code not_found and a message naming the method and path (without its query string). So a single error parser handles typos in a URL as well as real errors.
The complete list of codes, and every rate and size limit, is in Errors & limits.
Rate limits
All /v1/ requests share a limit of 100 requests per minute per IP address. When you exceed it you get 429 rate_limited with a Retry-After header (in seconds). Many endpoints also have their own tighter limits (uploads, key creation, tournament creation and so on). Those return 429 rate_limited without a Retry-After header. See Errors & limits for the full table.
Build in backoff: on 429, wait Retry-After seconds if present, or about a minute otherwise, then retry.
Pagination
Most list endpoints return a bounded list in one response (for example your games, reports and payouts return at most 200 items, newest first). No paging is needed.
The public catalogue (GET /v1/games) and game reviews (GET /v1/games/:slug/reviews) use cursor pagination:
curl "https://api.coolgptgames.com/v1/games?sort=new&limit=24"
# → { "games": [...], "nextCursor": "MjQ" }
curl "https://api.coolgptgames.com/v1/games?sort=new&limit=24&cursor=MjQ"
# → { "games": [...], "nextCursor": null } ← last pagePass
nextCursorback ascursorto get the next page.nullmeans you've reached the end.Treat cursors as opaque strings. Don't build them yourself.
GET /v1/gamesacceptslimitfrom 1 to 48 (default 24). Reviews come 20 per page.Paging stops at about 10,000 items.
nextCursorisnullas soon as the next page would cross that line, so a loop that followsnextCursoralways terminates. A cursor you build yourself that points past the ceiling is rejected with400 cursor_out_of_range. Narrow the query with filters or a different sort to reach older rows.A malformed or negative cursor is treated as the first page rather than an error.
The raw analytics export (GET /v1/dev/games/:id/analytics/events) pages differently: it uses a keyset cursor over the event timeline, so it has no depth ceiling. Its cursors are opaque too, and a corrupted one fails with 400 bad_cursor. See Remote config & analytics.
Idempotency
POST /v1/games (create a game) accepts an optional Idempotency-Key header (up to 128 characters). If you retry a create with the same key, you get the same draft back (200 with "idempotent": true) instead of a duplicate:
curl -X POST https://api.coolgptgames.com/v1/games \
-H "Authorization: Bearer $COOLGPTGAMES_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ci-build-4812" \
-d '{"title":"Space Miner","categorySlug":"arcade"}'No other endpoint takes an idempotency key. Many writes are naturally safe to repeat:
POST /v1/upload/completefor a version that already completed returns200with"idempotent": trueand the version's current state instead of processing it again.Remote config
PUTreplaces the whole config.Store items are upserted by
sku.Revoking an already-revoked key or cancelling an already-cancelled tournament does nothing new.
Reserving a version is not idempotent: every POST /v1/upload/init or POST /v1/games/:id/versions call reserves a new version number and counts against your hourly upload allowance.
Versioning
The API is versioned in the path (/v1/). There is no version header.
What can change without notice. Additive changes ship within v1 at any time: new endpoints, new optional request fields, new fields in a response, new values in a list that was already open-ended (a new reason code, a new tag). Write your client so it ignores fields it doesn't recognise and doesn't break on an unexpected enum value.
What we won't do without warning. These count as breaking, and we give at least 90 days' notice before any of them takes effect:
removing or renaming an endpoint, a request field, or a response field;
changing the type or meaning of an existing field;
making an optional request field required, or tightening validation on one;
changing an error's HTTP status or its
error.code;removing a scope, or requiring a new scope on an endpoint that already existed.
During those 90 days the old behaviour keeps working, and responses from a deprecated endpoint carry a Deprecation header with the date it stops.
How you'll hear about it. Changes are announced on these docs before they land. If a change affects keys on your account, we also email the address on the account. Tightening something for security or abuse is the one exception where we may act faster — we'll still tell you what changed and when.
Our side of the bargain. /v1 isn't going away without a /v2 that runs alongside it for at least 6 months.
Endpoint index
"Auth" says what the caller must be:
Public: no credentials needed. - Key or session: an API key or a signed-in session. - Session only: interactive session; API keys get
403 session_required.Player page: called for you by the game player while someone plays. You don't call it directly.
"Scope" is the API-key scope the endpoint checks. A signed-in session is never scope-limited. A publish key also satisfies read. None checked means any valid key for your account works, whatever scopes it holds. See API keys & scopes.
Your games: create, upload, manage
Detailed in Games & uploads API and Packaging & publishing.
Method | Path | Auth | Scope |
|---|---|---|---|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Presigned URL (no API key) | n/a |
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Public | n/a |
|
| Public | n/a |
Your dashboard data (/v1/me/*)
Documented in Creator dashboard endpoints below.
Method | Path | Auth | Scope |
|---|---|---|---|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session | None checked |
|
| Session only | n/a |
API keys
Detailed in API keys & scopes.
Method | Path | Auth | Scope |
|---|---|---|---|
|
| Session only | n/a |
|
| Session only | n/a |
|
| Session only | n/a |
Analytics and remote config
Detailed in Remote config & analytics.
Method | Path | Auth | Scope |
|---|---|---|---|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Public for a published game; the owner (or an admin) can also read their own |
|
GET /v1/dev/games/:id/analytics/events is the raw event export: keyset-paged, at most a 31-day range per call.
Webhooks
Detailed in Webhooks.
Method | Path | Auth | Scope |
|---|---|---|---|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
Tournaments
Detailed in Tournaments.
Method | Path | Auth | Scope |
|---|---|---|---|
|
| Key or session (session only when |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Public | n/a |
|
| Public | n/a |
|
| Public | n/a |
In-game economy
Detailed in In-game economy.
Method | Path | Auth | Scope |
|---|---|---|---|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Key or session |
|
|
| Public | n/a |
|
| Key or session | None checked |
GET /v1/dev/games/:id/ad-rewards/:rewardId is how your own server verifies a rewarded-ad receipt before granting the reward; see Ads & rewards.
Ranked matchmaking
Detailed in Ranked matchmaking.
Method | Path | Auth | Scope |
|---|---|---|---|
|
| Session only | n/a |
|
| Session only | n/a |
|
| Session only | n/a |
|
| Session only | n/a |
|
| Session only | n/a |
|
| Session only | n/a |
|
| Session only | n/a |
|
| Public | n/a |
Your game reaches all of these through GameSDK.ranked.*; the player page holds the session. An API key gets 403 session_required on every one except the public leaderboard. Each also takes a per-player rate limit before any work: polling (the queue and match reads, plus ratings) is capped at 20 per 10 seconds, writes (move, report, leave queue) at 120 a minute, and queue joins at 90 a minute.
Public game data
Useful for building your own pages, bots or leaderboards. No credentials needed.
Method | Path | Detailed in |
|---|---|---|
|
| This page (Pagination); Games & uploads API |
|
| Games & uploads API (your own unpublished game needs a session, or a key with |
|
| |
|
| |
|
| XP, achievements & social (needs a signed-in viewer) |
|
| Games & uploads API — the tag list, and what |
|
| This page (Pagination) |
|
| Your public creator profile and published games |
|
| Your public creator progression |
Called by the player page (not for direct use)
While someone plays your game, the player page calls these on their behalf, in response to GameSDK calls your game makes. They're listed so you recognise them in traces and limit tables. Don't call them from your own code; use the GameSDK instead. See How games run and the GameSDK pages.
Method | Path | Triggered by |
|---|---|---|
|
| The player loading and running your game |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
various |
|
|
Creator dashboard endpoints
These endpoints back the creator dashboard. Each returns only your own data. The read endpoints need a key with read (or publish); preview and rollback need publish. A signed-in session can call all of them.
GET /v1/me/games
Lists your games, including drafts, newest-updated first (up to 200). Deleted games are excluded.
{
"games": [
{
"id": "7c1f…",
"slug": "space-miner-3fa2c1",
"title": "Space Miner",
"status": "published",
"playsTotal": 1824,
"ratingAvg": 4.3,
"publishedAt": "2026-09-02T18:11:04.000Z",
"updateInReview": false
}
]
}status is one of draft, scanning, pending_review, published, rejected, delisted, dmca_removed. updateInReview is true when a live game has a newer version still in moderation — the one case where status stays published and tells you nothing.
The public GET /v1/games/:slug returns 404 for a game that isn't published. Use this endpoint and GET /v1/me/games/:id to see your drafts.
Deleted a game by mistake? GET /v1/me/deleted-games lists the last 30 days of deletions with a restorable flag, and POST /v1/games/:id/restore puts one back. See Games & uploads API.
GET /v1/me/games/:id
The owner-only manage view for one game: metadata, every version with its moderation result, and per-day revenue.
{
"game": {
"id": "7c1f…", "slug": "space-miner-3fa2c1", "title": "Space Miner",
"description": "…", "instructions": "…",
"status": "published", "visibility": "full",
"category": { "slug": "arcade", "name": "Arcade" },
"aiGenerated": true, "aiDisclosure": "…",
"playsTotal": 1824, "ratingAvg": 4.3, "ratingCount": 51,
"currentVersionId": "b9e0…",
"tags": ["pixel-art", "roguelike"], "websites": ["https://example.com"],
"publishedAt": "…", "createdAt": "…", "updatedAt": "…"
},
"versions": [
{
"id": "b9e0…", "versionNumber": 3, "status": "live", "phase": "live",
"realtimeEnabled": false, "connectHosts": [], "changelog": "New level 5.",
"sizeBytes": 481223,
"runtime": "html5", "runtimeVersion": "1.0.0",
"createdAt": "…", "isCurrent": true,
"moderation": { "verdict": "auto_approve", "confidence": 0.97, "status": "complete", "error": null },
"review": null, "appeal": null
}
],
"analytics": {
"earningsTotalUsd": 12.4,
"byDay": [{ "date": "2026-09-20", "validPlays": 88, "impressions": 140, "grossUsd": 0.61, "creatorShareUsd": 0.3 }],
"reportsOpen": 0
}
}visibilityislimited(reachable by direct link, not yet shown in public discovery) orfull.Version
statusisuploaded,scanning,passed,failedorlive, andphaseis the plain reading of it:live,approved,rejected,in_human_review,superseded,scanningorawaiting_upload. Branch onphase.changelogandconnectHostsare what you sent with that upload.moderationis the automated scan result.verdictisauto_approve,auto_rejectorneeds_review, andstatusisqueued,running,completeorerror.reviewis the latest recorded decision,{ decision, reasonCodes, notes, automated, at }withdecisioneitherapproveorreject, when one exists. Human and automated decisions both land here, distinguished byautomated. An automated one hasnotesof"Approved by automated moderation."or"Rejected by automated moderation.". If a version was rejected, the reason codes are here.appealis your appeal of a rejected version, if any:{ id, status: "open" | "approved" | "upheld", note, resolutionNotes, createdAt, resolvedAt }.
Errors: 404 not_found if the game doesn't exist or was deleted; 403 forbidden if it isn't yours.
When you ship an update to a game that's already live, the game's status stays published while the new version is moderated.
GET /v1/me/games/:id/versions/:versionId
The same version object for one version, plus the game's own status — the cheap thing to poll after an upload.
{ "version": { "id": "…", "versionNumber": 4, "status": "scanning", "phase": "scanning", "…": "…" },
"gameStatus": "published", "gameSlug": "space-miner-3fa2c1" }Poll it with the uploadId you got from upload/init and wait for version.phase to leave scanning. Full field list in Games & uploads API.
POST /v1/me/games/:id/versions/:versionId/appeal
Appeals a version that moderation rejected, with a note of 10–2,000 characters. One appeal per version, 5 a day per account. A moderator reads it next to that version's changelog and can overturn the rejection, which publishes the version and fires game.approved with appeal: true. See Games & uploads API and Packaging & publishing.
GET /v1/me/games/:id/preview
Returns a private, short-lived play link for your game's build, including a build that isn't published yet. Use it to play-test before or during review.
Query | Meaning |
|---|---|
| Preview a specific version. Default: the current version, or else the newest upload. |
{
"playUrl": "https://…?t=…",
"expiresAt": "2026-09-21T09:15:00.000Z",
"version": { "id": "…", "versionNumber": 4, "status": "scanning", "realtimeEnabled": false, "changelog": null, "isCurrent": false },
"game": { "id": "…", "slug": "space-miner-3fa2c1", "title": "Space Miner", "status": "published", "runtimeSlug": "html5", "loadingImageUrl": "https://…" }
}The link carries an access ticket that expires after 15 minutes (expiresAt). Request a new one each time you need it. The game block lets a player render its chrome without a second call: the dashboard's preview page uses it to run the build inside the real player in preview mode, where the SDK is answered but nothing is written.
Errors: 400 no_build (nothing uploaded yet), 400 runtime (runtime unavailable for preview), 403/404 as above.
POST /v1/me/games/:id/versions/:versionId/rollback
Makes an earlier version the live one again, instantly and without re-review. Only versions that were approved and published before (status live) qualify. A passed version, which is still waiting for a human reviewer, doesn't. The game becomes published with full visibility.
{ "ok": true, "currentVersionId": "a13c…", "versionNumber": 2 }The rolled-back version's declared achievements are re-activated with it, exactly as approval would: its set is upserted and anything it doesn't declare is deactivated.
Errors: 400 not_approved (that version was never approved and published), 409 game_removed (the game was delisted or DMCA-removed), 404 (unknown game or version, or a deleted game).
GET /v1/me/summary
Headline numbers for your account.
{
"playsTotal": 5120, "gamesTotal": 4, "gamesLive": 3, "gamesPending": 1, "reportsOpen": 0,
"lifetimeEarningsUsd": 41.2, "last30EarningsUsd": 9.85,
"availableUsd": 41.2, "pendingPayoutUsd": 0,
"payoutStatus": "none", "payoutMinimumUsd": 50, "creatorRevenueSharePct": 50
}GET /v1/me/earnings
Per-day earnings across all your games.
Query | Meaning |
|---|---|
| First date to include, |
| Last date to include, |
{
"totals": { "grossUsd": 82.4, "creatorShareUsd": 41.2, "validPlays": 5020, "impressions": 7310 },
"byDay": [{ "date": "2026-09-20", "grossUsd": 1.2, "creatorShareUsd": 0.6, "validPlays": 88, "impressions": 140 }],
"payoutMinimumUsd": 50
}GET /v1/me/payouts
Your payout history (up to 200, newest first) and your current unpaid balance.
{
"payouts": [{ "id": "…", "periodStart": "…", "periodEnd": "…", "amountUsd": 55.1, "status": "paid", "createdAt": "…", "paidAt": "…" }],
"availableUsd": 12.4,
"payoutStatus": "none",
"payoutMinimumUsd": 50
}Setting up where payouts go (POST /v1/me/connect/onboard) is session-only. Do it in the dashboard.
GET /v1/me/reports
Player reports filed against your games (up to 200, newest first). This is read-only: moderators resolve reports, and you can't dismiss them.
{
"reports": [{ "id": "…", "gameId": "…", "gameTitle": "Space Miner", "gameSlug": "space-miner-3fa2c1", "reasonCode": "broken", "details": "…", "status": "open", "createdAt": "…" }],
"openCount": 1
}GET /v1/me/creator
Your creator progression (creator level, XP and unlocked perks), recalculated on each call. Higher creator levels raise your hourly upload allowance (see Errors & limits).
Auth: session, or a key with read.
{
"xp": 1450,
"level": 6,
"title": "Indie Dev",
"xpIntoLevel": 250,
"xpForLevel": 600,
"progress": 0.416,
"unlockedCount": 4,
"totalAchievements": 14,
"perks": { "uploadsPerHour": 12, "priorityModeration": false, "featured": false },
"perkUnlocks": [
{ "level": 3, "icon": "⬆️", "name": "8 uploads / hour", "description": "…", "unlocked": true }
],
"stats": {
"gamesPublished": 3,
"totalPlays": 1234,
"categoriesCovered": 2,
"totalCategories": 12,
"totalReviews": 7
},
"achievements": [
{
"key": "first_launch",
"name": "First Launch",
"description": "…",
"tier": "common",
"icon": "🚀",
"xpReward": 100,
"unlocked": true,
"progress": { "current": 1, "target": 1 }
}
]
}Field | Meaning |
|---|---|
| XP needed to finish this level, or |
|
|
| What your level unlocks right now; |
| The full creator achievement catalogue in display order, each with your progress |
| Counted from your published, non-deleted games |
Creator achievements and levels are separate from the player XP your games award.
GET /v1/auth/me
Returns the account the credentials belong to, as { "user": { … } }, or 401 unauthorized. Useful as a "does this key work?" check.
See also
API keys & scopes: creating keys and choosing scopes - Errors & limits: every error code and limit - Games & uploads API: the publishing flow in detail - Publisher SDK, CLI, MCP server: wrappers over this API