Errors & limits
This page lists, in one place, every error code the Cool GPT Games API returns, how codes map to HTTP statuses, and every rate and size limit you can run into. For the error envelope and general conventions, see REST API overview.
The error envelope
Every handled error looks like this:
{
"error": {
"code": "too_many_webhooks",
"message": "Up to 10 webhooks per account. Delete one first.",
"details": null
}
}Branch on
code, not onmessage. Codes are stable. Messages are for humans and can change.detailsis only filled in on some errors. Forvalidation_errorit holds the field-by-field problems.There are no exceptions. A request to a path that doesn't exist also uses this envelope:
404with codenot_foundand a message naming the method and path (the query string is stripped, so a token in a mistyped URL never comes back in the response). One error parser covers everything.
Status codes at a glance
Status | Meaning | What to do |
|---|---|---|
| The request was invalid (bad field, failed a rule, bundle rejected) | Fix the request. Retrying unchanged will fail again. |
| No valid credentials: missing, wrong or revoked key, expired session, or banned/deleted account | Check the key. Revoked keys never come back. |
| Authenticated but not allowed: not your game, missing scope, session-only route, CSRF failure | Use the right key or scope, or do it in the dashboard |
| Doesn't exist, or you can't see it (for example another creator's draft) | Check the id or slug |
| Conflict with existing state | Read the message. Usually needs a user action. |
| Request body over the endpoint's size limit | Send less. See Size limits. |
| Unsupported | Use JSON, |
| Rate limited | Back off and retry later. See Rate limits. |
| Unexpected server error | Retry with backoff. Report it if it persists. |
| A dependency is temporarily unavailable | Retry with backoff |
Error codes
Authentication, permissions and request format
These can come from any endpoint.
Code | Status | When |
|---|---|---|
| 401 | No credentials, or the key/session isn't valid: wrong, revoked, expired, or the account is banned or deleted |
| 403 | You're not allowed: not your game, or (message |
| 403 | An API key called a route that needs a signed-in session: account and key management, payouts setup, anything that spends or moves coins (daily claim, coin gifts, shop purchases, season claims, in-game purchases and consumes, funding a tournament prize pool), every per-player ranked route, and the DMCA counter-notice endpoints. Do it signed in on coolgptgames.com. See API keys & scopes. |
| 404 | The resource doesn't exist, isn't visible to you, or there's no route at that path |
| 400 | The body or query failed validation: missing field, wrong type, too long, bad enum value. |
| 400 | Malformed request, for example a body that isn't valid JSON |
| 400 | A pagination cursor points past the ~10,000-item ceiling. Narrow the query rather than paging deeper. See Pagination. |
| 413 | Body larger than the endpoint allows (1 MB by default) |
| 415 |
|
| 429 | A rate limit was hit. See Rate limits. |
| 503 | The sign-in/key-verification service is briefly unreachable. Your key is probably fine, so retry with backoff. |
| 500 | Unexpected server error |
API keys
Code | Status | Endpoint | When |
|---|---|---|---|
| 409 |
| Your session predates the current sign-in system. Sign out and back in, then create the key. |
| 429 |
| More than 10 keys created in an hour |
Creating and editing games
Code | Status | Endpoint | When |
|---|---|---|---|
| 400 |
| Human verification failed. Where the check is configured it fails closed for browser sessions: a session request with no token, or an invalid one, is refused. API keys are exempt and send none. |
| 400 |
| A |
| 400 | same | More than 5 tags on one game |
Unpublishing and restoring
Code | Status | Endpoint | When |
|---|---|---|---|
| 409 |
| The game isn't deleted (or another request restored it first) |
| 409 | same | More than 30 days since the delete |
| 409 | same | The game was delisted or DMCA-removed before it was deleted. Restore never lifts a takedown. |
| 409 | same | Deleted before self-serve restore existed, so there's no prior status to return to. Contact support. |
Appeals
Code | Status | Endpoint | When |
|---|---|---|---|
| 409 |
| Only a version rejected by moderation can be appealed. A build that failed validation at |
| 409 | same | This version has already been appealed. Upload a new version instead. |
| 409 | same, and moderator resolution | A newer version has been uploaded since, so approving this one would roll the game back |
| 409 | same | The game has been taken down; its versions can't be appealed |
| 409 | moderator resolution | The appeal has already been decided |
| 429 |
| More than 5 appeals in a day. A refused appeal doesn't spend the allowance. |
Copyright (DMCA)
Code | Status | Endpoint | When |
|---|---|---|---|
| 409 |
| That game wasn't removed after a DMCA notice |
| 409 | same | A counter-notice for this game is already being reviewed |
| 403 | same, and | A counter-notice is a sworn statement, so API keys are refused |
| 429 |
| More than 5 counter-notices in a day |
Uploading builds
See Games & uploads API and Packaging & publishing for the full flow.
Code | Status | Endpoint | When |
|---|---|---|---|
| 400 |
| The |
| 400 | same | The runtime exists but isn't accepting uploads |
| 400 | same | A |
| 400 | same | More than 10 |
| 429 |
| Hourly upload allowance used up (message |
| 409 |
| The game was delisted or removed after a copyright (DMCA) notice. A taken-down game can't take new builds. |
| 404 | same | The game doesn't exist or was deleted (unpublished) |
| 400 |
| The game has no cover image. Upload one with |
| 400 |
| Nothing was uploaded to the |
Calling upload/complete again for a version that already completed isn't an error: it returns 200 with "idempotent": true and the version's current state.
If upload/complete rejects the bundle itself, the version is marked failed and the error code says why:
Code | When |
|---|---|
| The uploaded file is empty |
| The zip is over the runtime's upload limit (the |
| The unpacked contents are over 300 MB |
| More than 2,000 files in the zip |
| A file compresses more than 100:1 (zip-bomb guard) |
| The upload isn't a |
| A path in the zip contains |
| A path in the zip is absolute ( |
| The zip contains a symbolic link |
| The file isn't a readable zip |
| Unpacking took too long (over about 20 seconds) |
| No |
| The zip doesn't look like an export from the chosen runtime's tool (for example a PICO-8, Twine or Bitsy web export) |
| The runtime configuration couldn't be read |
The fix is always to change the bundle and upload again with a new upload/init.
Game images
Code | Status | Endpoint | When |
|---|---|---|---|
| 400 |
| Empty body |
| 400 | same | Image over 5 MB |
| 400 | same | Not a PNG, JPEG, GIF or WebP (checked from the file bytes) |
| 400 | same | Already 8 screenshots. Delete one first. |
| 429 | same | More than 60 image uploads in an hour from your IP |
Preview and rollback
Code | Status | Endpoint | When |
|---|---|---|---|
| 400 |
| The game has no uploaded build yet |
| 400 | same | The build's runtime can't be previewed right now |
| 400 |
| The version was never approved and published (its status isn't |
| 409 | same | The game was delisted or removed after a DMCA notice. Rolling back can't bring it back. |
| 404 | preview, rollback | No such game or version, or the game was deleted |
Remote config, analytics and webhooks
Code | Status | Endpoint | When |
|---|---|---|---|
| 400 |
| The config JSON is 16 KB or larger |
| 400 |
| Already 10 webhooks on the account |
| 400 |
| URL isn't |
| 400 | Analytics events (via | More than 50 properties on one event |
| 400 | same | Event properties are 4,000 bytes or more as JSON |
| 400 |
|
|
| 400 | same | The export window is longer than 31 days. Ask for it in chunks. |
| 400 | same | The |
| 404 |
| No such game, or it isn't published and you aren't its owner. The public config endpoint serves published games only. |
Tournaments
See Tournaments.
Code | Status | Endpoint | When |
|---|---|---|---|
| 400 |
| Neither |
| 400 | same | The game must be published to run a tournament |
| 400 | same |
|
| 400 | same | Your coin balance can't cover |
| 403 | same |
|
| 403 | same | The game isn't yours |
| 429 | same | More than 20 tournaments created in an hour |
| 400 |
| The tournament is already settled or cancelled |
| 400 | same | The tournament can't be cancelled in its current state |
| 400 |
| The tournament isn't open for entries |
In-game economy
See In-game economy.
Code | Status | Endpoint | When |
|---|---|---|---|
| 400 |
| Already 200 items on the game (editing an existing |
| 400 | same | Players already own this SKU, so it can't switch between |
| 400 | Purchases (via | The player can't afford the item |
| 400 | same | A |
| 409 | same | That |
| 400 |
| Only a consumable item can be consumed |
| 400 | same | The player owns fewer units than you asked to spend. |
| 403 | Purchases and consumes | Called with an API key. Both are made by the signed-in player through the player page. |
| 429 | Purchases | More than 60 purchases per minute by one player |
Ads and rewards
See Ads & rewards. A capped or unfillable request isn't an HTTP error your game sees: requestAd resolves with a reason and fires adError.
Code / reason | Status | Where | When |
|---|---|---|---|
| — |
| The server-side frequency cap for that placement is spent. Carries |
| — | same | No ad source is set up for that placement. Interstitial and mid-roll resolve this way today; rewarded ads work. |
| — | same | The network had nothing to show |
| — | same | The placement needs a play session and there isn't one |
| — | the SDK, in a dashboard preview | Preview mode never requests or meters an ad. See Packaging & publishing. |
| 400 |
| Only a rewarded ad can be reported as rewarded |
| 400 | same | The rewarded ad hasn't run long enough to have finished |
| 409 | same | That slot is already in a final state. |
Saves, scores and ranked play
These are reached through the GameSDK. See Saves & cloud progress, Scores, leaderboards & anti-cheat and Ranked matchmaking.
Code | Status | When |
|---|---|---|
| 400 | The player already has 100 save slots for this game. Delete one first. |
| 400 | A save value over 64 KB, or a key over 128 characters |
| 400 | A ranked move was submitted out of turn |
| 400 | A ranked move is missing a state hash. Once any move in a match has sent an |
Payouts
Code | Status | Endpoint | When |
|---|---|---|---|
| 400 |
| Add an email to your account before setting up payouts |
Multiplayer relay
The real-time relay reports problems as error events on the room, not as HTTP errors: room_full, game_room_limit, rate_limited and too_large (an oversized message is dropped and the connection stays open). Private ranked match rooms add two WebSocket close codes: 4401 (ticket_required: no valid room ticket) and 4409 (the same player connected again, so the older socket was replaced). See Multiplayer and Ranked matchmaking.
Player and community codes
You'll mainly see these in the website, not in developer tooling. They're listed so the list is complete.
Code | Status | When |
|---|---|---|
| 400 | Trying to friend, follow, block, report or gift coins to yourself |
| 409 | Either player is already at the 1,000-friend maximum. The message says whether it's you or them. |
| 409 | You already have 100 outgoing friend requests waiting. Cancel a few, or wait for answers. |
| 400 | Coin gifts only go to friends |
| 400 | Not enough coins (platform shop, gifts) |
| 400 | Daily reward or season tier already claimed |
| 400 | Season tier not reached yet |
| 400 | No such season tier |
| 400 | Platform cosmetics shop problems |
| 400 | No pending friend request from that player |
| 400 | A comment or review was blocked by the content filter |
| 400 | Over 100 collections, or over 500 games in one collection |
| 400 | That game can't be pinned to a profile |
| 409 | The email is already used by another account |
| 409 | That sign-in provider is linked to a different account |
| 400 | Email verification problems |
| 410 | A retired sign-in endpoint. Sign in from the login page. |
Rate limits
Limits use fixed time windows. When one is exceeded the request fails with 429 rate_limited. Only the global per-IP limit sends a Retry-After header (seconds until the window resets). For the others, wait for the window to roll over.
Global
Limit | Applies to | Counted per |
|---|---|---|
100 requests / minute | Every | Client IP address |
Everything else below applies in addition to this limit.
Developer endpoints
Limit | Endpoint(s) | Counted per |
|---|---|---|
Hourly upload allowance: 5 / hour, rising with creator level (see below) |
| Account |
60 / hour |
| Client IP |
10 / hour |
| Account |
20 / hour |
| Account |
120 / minute |
| Account |
5 / day |
| Account |
5 / day |
| Account |
Upload allowance by creator level (see GET /v1/me/creator):
Creator level | Uploads per hour |
|---|---|
1–2 | 5 |
3–5 | 8 |
6–9 | 12 |
10–14 | 20 |
15–24 | 30 |
25+ | 50 |
Game and player activity
These are counted as players play your game. Size your game's calls so a normal player never gets near them. The GameSDK pages explain how each call behaves when a limit is hit.
Limit | What it covers | Counted per |
|---|---|---|
60 / hour | Starting play sessions | Client IP |
120 / minute | XP awards ( | Player + game |
60 / minute | Achievement unlocks ( | Player + game |
30 / minute | Score and replay submissions ( | Player + game |
120 / minute | Cloud-save writes ( | Player + game |
60 / minute | In-game store purchases ( | Player |
30 / hour | Friend requests ( | Player |
90 / minute | Ranked queue joins ( | Player |
20 / 10 seconds | Ranked polling: the queue and match reads, and rating lookups ( | Player |
120 / minute | Ranked writes: moves, result reports, leaving the queue | Player |
Ranked play polls at about 1–1.5 s while searching or waiting for a turn, which sits comfortably inside the 20-per-10-seconds budget. There are also friend caps rather than rates: 1,000 friends and 100 open outgoing requests per player (friend_limit, friend_request_limit).
The player page applies two more limits of its own. It accepts at most 30 messages per second from a game, and ignores the extra messages. It forwards at most 8 friend requests per play.
Account and community
Limit | What it covers | Counted per |
|---|---|---|
10 / 15 minutes | Sign-in attempts | Client IP |
Multiplayer relay
Limit | Value |
|---|---|
Players per room | 64 (2 in a private ranked match room) |
Rooms per game | 200 |
Message size | 8 KB |
Messages per second, per player | 30 |
Retained state snapshot per player | 8 KB |
Concurrent connections per IP | 20 |
See Multiplayer.
Handling 429s
If
Retry-Afteris present, wait that many seconds.Otherwise back off exponentially, starting at a few seconds, capped at the window length (1 minute or 1 hour depending on the limit).
Don't retry uploads in a tight loop: each
upload/initorgames/:id/versionscall counts against your hourly allowance even if the upload later fails.CI runners that share an outbound IP share the 100/minute IP budget.
Pagination and cursors
Two different paging schemes, with different failure modes.
Offset cursors (the public catalogue GET /v1/games, game reviews) address a position in a list:
Paging stops at about 10,000 items.
nextCursorcomes backnullas soon as the next page would cross that line, so a loop that followsnextCursoralways terminates.A cursor you construct yourself that points past the ceiling is rejected with
400 cursor_out_of_range. Filter or sort differently to reach older rows.A malformed, negative or non-numeric cursor is treated as the first page rather than an error.
Keyset cursors (the raw analytics export GET /v1/dev/games/:id/analytics/events) address the last row you saw, so there's no depth ceiling — you can walk a whole month of events. A cursor the endpoint didn't issue fails with 400 bad_cursor, and the range itself is capped at 31 days (range_too_large) with from before to (bad_range).
Cursors of either kind are opaque. Pass nextCursor back unchanged; don't parse or build one.
Size limits
Requests
Limit | Value | Applies to |
|---|---|---|
Request body | 1 MB | Every endpoint unless listed below |
Bundle upload | Per runtime, see below | The presigned |
| 128 characters (longer values are cut) |
|
Page size | 1–48 (default 24) |
|
Pagination depth | About 10,000 items ( | Offset-cursor lists — see Pagination and cursors |
Export page size | 1–1,000 (default 500) |
|
Export range | 31 days per call (default: the last 7) | same |
Bundles
Limit | Value |
|---|---|
Upload size, HTML5 | 100 MB |
Upload size, Twine | 10 MB |
Upload size, PICO-8 | 8 MB |
Upload size, Bitsy | 8 MB |
Unpacked size | 300 MB |
Files per bundle | 2,000 |
Compression ratio per file | 100:1 |
Unpacking time | About 20 seconds |
Presigned upload URL lifetime | 15 minutes |
External hosts ( | 10 per version |
Declared achievements | 30 per game |
GET /v1/runtimes returns each runtime's current maxUploadBytes and accepted extensions. Treat that as the authoritative value: upload/complete enforces it, and a bigger zip fails with upload_too_large.
Bundles may contain only these file types: .html, .htm, .js, .mjs, .css, .json, .map, .txt, .wasm, .png, .jpg, .jpeg, .gif, .webp, .svg, .ico, .mp3, .ogg, .wav, .woff, .woff2, .ttf, .glb, .gltf, .bin, .pck, .data, .br, .gz. OS clutter (__MACOSX/ folders, .DS_Store, Thumbs.db, desktop.ini) is skipped silently. See Packaging & publishing.
Game metadata
Field | Limit |
|---|---|
| 1–120 characters |
| 4,000 characters each |
| 160 characters |
| 8,000 characters |
| 20 entries; question 300 characters, answer 2,000 |
| 5 URLs, each |
| 5 per game, from |
| 2,000 characters |
Version | 2,000 characters |
Appeal | 10–2,000 characters |
Screenshots | 8 per game |
Images | PNG, JPEG, GIF or WebP; 5 MB each |
Developer features
Feature | Limit |
|---|---|
API key name | 1–80 characters |
Remote config | Under 16 KB of JSON |
Webhooks | 10 per account; URL must be |
Store items | 200 per game; |
Tournaments |
|
Player data your game writes
Feature | Limit |
|---|---|
Save slot value | 64 KB |
Save slot key | 1–128 characters |
Save slots | 100 per player per game |
Analytics event name | 1–64 characters (letters, digits, |
Analytics event properties | 50 keys, under 4,000 bytes as JSON |
Friends | 1,000 per player; 100 open outgoing requests ( |
Reported score | Scores above 1,000,000,000,000 are rejected as anomalies |
Tokens and links
Item | Lifetime |
|---|---|
Play link ticket ( | 15 minutes |
API keys | No expiry; valid until revoked |
See also
REST API overview: conventions and the endpoint index - API keys & scopes:
401/403causes and scopesGames & uploads API, Packaging & publishing: the upload flow these limits apply to