Errors & limits

Embed
Preview of the card
<a href="https://coolgptgames.com/docs/errors-and-limits"><img src="https://coolgptgames.com/docs/_api/public/orgs/062b11d9-2b86-4627-9e86-96816a67d7a1/spaces/07d97e67-936e-4223-9eaf-6998a46fee24/pages/b279debc-ba5e-40ac-b776-0b9bee5e37e2/social-card.svg?v=3e7ae33eec8b" alt="Errors &amp; limits" width="600" height="315"></a>

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 on message. Codes are stable. Messages are for humans and can change.

  • details is only filled in on some errors. For validation_error it holds the field-by-field problems.

  • There are no exceptions. A request to a path that doesn't exist also uses this envelope: 404 with code not_found and 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

400

The request was invalid (bad field, failed a rule, bundle rejected)

Fix the request. Retrying unchanged will fail again.

401

No valid credentials: missing, wrong or revoked key, expired session, or banned/deleted account

Check the key. Revoked keys never come back.

403

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

404

Doesn't exist, or you can't see it (for example another creator's draft)

Check the id or slug

409

Conflict with existing state

Read the message. Usually needs a user action.

413

Request body over the endpoint's size limit

Send less. See Size limits.

415

Unsupported Content-Type

Use JSON, application/zip or a supported image type

429

Rate limited

Back off and retry later. See Rate limits.

500

Unexpected server error

Retry with backoff. Report it if it persists.

503

A dependency is temporarily unavailable

Retry with backoff

Error codes

Authentication, permissions and request format

These can come from any endpoint.

Code

Status

When

unauthorized

401

No credentials, or the key/session isn't valid: wrong, revoked, expired, or the account is banned or deleted

forbidden

403

You're not allowed: not your game, or (message This API key lacks the "<scope>" scope.) the key is missing a required scope (game, upload and media writes need publish; /v1/me/* reads need read), or (message CSRF token missing or invalid) a cookie-authenticated write without the X-CSRF-Token header

session_required

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.

not_found

404

The resource doesn't exist, isn't visible to you, or there's no route at that path

validation_error

400

The body or query failed validation: missing field, wrong type, too long, bad enum value. details lists each problem.

bad_request

400

Malformed request, for example a body that isn't valid JSON

cursor_out_of_range

400

A pagination cursor points past the ~10,000-item ceiling. Narrow the query rather than paging deeper. See Pagination.

payload_too_large

413

Body larger than the endpoint allows (1 MB by default)

unsupported_media_type

415

Content-Type the endpoint doesn't accept

rate_limited

429

A rate limit was hit. See Rate limits.

atlas_unavailable

503

The sign-in/key-verification service is briefly unreachable. Your key is probably fine, so retry with backoff.

internal

500

Unexpected server error

API keys

Code

Status

Endpoint

When

atlas_link_required

409

POST /v1/me/keys

Your session predates the current sign-in system. Sign out and back in, then create the key.

rate_limited

429

POST /v1/me/keys

More than 10 keys created in an hour

Creating and editing games

Code

Status

Endpoint

When

captcha_failed

400

POST /v1/games

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.

unknown_tag

400

POST /v1/games, POST /v1/games/:id

A tags entry isn't on the curated list. The message names the offending tags; fetch the list from GET /v1/tags.

too_many_tags

400

same

More than 5 tags on one game

Unpublishing and restoring

Code

Status

Endpoint

When

not_deleted

409

POST /v1/games/:id/restore

The game isn't deleted (or another request restored it first)

restore_window_expired

409

same

More than 30 days since the delete

game_removed

409

same

The game was delisted or DMCA-removed before it was deleted. Restore never lifts a takedown.

restore_unavailable

409

same

Deleted before self-serve restore existed, so there's no prior status to return to. Contact support.

Appeals

Code

Status

Endpoint

When

not_rejected

409

POST /v1/me/games/:id/versions/:versionId/appeal

Only a version rejected by moderation can be appealed. A build that failed validation at complete has no decision to overturn.

appeal_exists

409

same

This version has already been appealed. Upload a new version instead.

appeal_superseded

409

same, and moderator resolution

A newer version has been uploaded since, so approving this one would roll the game back

game_removed

409

same

The game has been taken down; its versions can't be appealed

appeal_resolved

409

moderator resolution

The appeal has already been decided

rate_limited

429

POST /v1/me/games/:id/versions/:versionId/appeal

More than 5 appeals in a day. A refused appeal doesn't spend the allowance.

Code

Status

Endpoint

When

not_dmca_removed

409

POST /v1/dmca/counter-notices

That game wasn't removed after a DMCA notice

counter_notice_exists

409

same

A counter-notice for this game is already being reviewed

session_required

403

same, and GET /v1/me/dmca-removals

A counter-notice is a sworn statement, so API keys are refused

rate_limited

429

POST /v1/dmca/counter-notices

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

unknown_runtime

400

POST /v1/upload/init, POST /v1/games/:id/versions

The runtime isn't one the platform supports

runtime_disabled

400

same

The runtime exists but isn't accepting uploads

bad_connect_hosts

400

same

A connectHosts entry isn't an exact external https:// host (no IPs, localhost, wildcards, paths or Cool GPT Games' own origins)

too_many_connect_hosts

400

same

More than 10 connectHosts

rate_limited

429

POST /v1/upload/init, POST /v1/games/:id/versions

Hourly upload allowance used up (message Upload limit reached). Both endpoints share one allowance.

game_removed

409

POST /v1/upload/init, POST /v1/games/:id/versions, POST /v1/upload/complete

The game was delisted or removed after a copyright (DMCA) notice. A taken-down game can't take new builds.

not_found

404

same

The game doesn't exist or was deleted (unpublished)

cover_required

400

POST /v1/upload/complete

The game has no cover image. Upload one with POST /v1/games/:id/media?kind=cover first. Only an image uploaded through that endpoint counts.

no_upload

400

POST /v1/upload/complete

Nothing was uploaded to the uploadUrl for this version

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

empty_upload

The uploaded file is empty

upload_too_large

The zip is over the runtime's upload limit (the maxUploadBytes from GET /v1/runtimes)

uncompressed_too_large

The unpacked contents are over 300 MB

too_many_files

More than 2,000 files in the zip

compression_ratio_exceeded

A file compresses more than 100:1 (zip-bomb guard)

disallowed_extension

The upload isn't a .zip, or the zip contains a file type the runtime doesn't allow

path_traversal

A path in the zip contains .., a backslash or a null byte

absolute_path

A path in the zip is absolute (/… or C:\…)

symlink

The zip contains a symbolic link

invalid_archive

The file isn't a readable zip

extraction_timeout

Unpacking took too long (over about 20 seconds)

missing_entry

No index.html in the bundle

invalid_cart_format

The zip doesn't look like an export from the chosen runtime's tool (for example a PICO-8, Twine or Bitsy web export)

invalid_config

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

no_image

400

POST /v1/games/:id/media

Empty body

too_large

400

same

Image over 5 MB

bad_image

400

same

Not a PNG, JPEG, GIF or WebP (checked from the file bytes)

too_many

400

same

Already 8 screenshots. Delete one first.

rate_limited

429

same

More than 60 image uploads in an hour from your IP

Preview and rollback

Code

Status

Endpoint

When

no_build

400

GET /v1/me/games/:id/preview

The game has no uploaded build yet

runtime

400

same

The build's runtime can't be previewed right now

not_approved

400

POST /v1/me/games/:id/versions/:versionId/rollback

The version was never approved and published (its status isn't live). A version that only cleared the automated checks and is still waiting for a human (passed) doesn't qualify.

game_removed

409

same

The game was delisted or removed after a DMCA notice. Rolling back can't bring it back.

not_found

404

preview, rollback

No such game or version, or the game was deleted

Remote config, analytics and webhooks

Code

Status

Endpoint

When

too_large

400

PUT /v1/dev/games/:id/config

The config JSON is 16 KB or larger

too_many_webhooks

400

POST /v1/dev/webhooks

Already 10 webhooks on the account

validation_error

400

POST /v1/dev/webhooks

URL isn't https://, is over 500 characters, or an event name is unknown

too_many_props

400

Analytics events (via GameSDK.trackEvent)

More than 50 properties on one event

props_too_large

400

same

Event properties are 4,000 bytes or more as JSON

bad_range

400

GET /v1/dev/games/:id/analytics/events

from isn't before to

range_too_large

400

same

The export window is longer than 31 days. Ask for it in chunks.

bad_cursor

400

same

The cursor isn't one this endpoint issued. Pass nextCursor back unchanged.

not_found

404

GET /v1/games/:slug/config

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

no_game

400

POST /v1/dev/tournaments

Neither gameId nor gameSlug was given

not_published

400

same

The game must be published to run a tournament

bad_window

400

same

endsAt isn't after startsAt

insufficient_coins

400

same

Your coin balance can't cover prizePoolCoins

session_required

403

same

prizePoolCoins is above 0 and the request used an API key. Funding a prize pool needs a signed-in session. Free tournaments work with a key.

forbidden

403

same

The game isn't yours

rate_limited

429

same

More than 20 tournaments created in an hour

already_final

400

POST /v1/dev/tournaments/:id/cancel

The tournament is already settled or cancelled

cannot_cancel

400

same

The tournament can't be cancelled in its current state

closed

400

POST /v1/tournaments/:id/join (via GameSDK.tournaments.join)

The tournament isn't open for entries

In-game economy

See In-game economy.

Code

Status

Endpoint

When

too_many_items

400

POST /v1/dev/games/:id/items

Already 200 items on the game (editing an existing sku still works)

kind_locked

400

same

Players already own this SKU, so it can't switch between durable and consumable. Add a new SKU instead.

insufficient_coins

400

Purchases (via GameSDK.economy.purchase)

The player can't afford the item

bad_quantity

400

same

A quantity above 1 on a durable item — those are bought one at a time

idempotency_conflict

409

same

That idempotencyKey was already used for a different item. Use a fresh key per purchase.

not_consumable

400

GameSDK.economy.consume

Only a consumable item can be consumed

insufficient_quantity

400

same

The player owns fewer units than you asked to spend. details.available says how many are left.

session_required

403

Purchases and consumes

Called with an API key. Both are made by the signed-in player through the player page.

rate_limited

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

capped

—

POST /v1/ads/request (via GameSDK.requestAd)

The server-side frequency cap for that placement is spent. Carries retryAfterSec when it's the pacing interval rather than a hard per-session maximum.

not_configured

—

same

No ad source is set up for that placement. Interstitial and mid-roll resolve this way today; rewarded ads work.

no_fill

—

same

The network had nothing to show

no_session

—

same

The placement needs a play session and there isn't one

preview

—

the SDK, in a dashboard preview

Preview mode never requests or meters an ad. See Packaging & publishing.

not_rewarded_placement

400

POST /v1/ads/slots/:slotId/outcome

Only a rewarded ad can be reported as rewarded

too_soon

400

same

The rewarded ad hasn't run long enough to have finished

invalid_transition

409

same

That slot is already in a final state. details.status says which.

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

too_many_saves

400

The player already has 100 save slots for this game. Delete one first.

validation_error

400

A save value over 64 KB, or a key over 128 characters

not_your_turn

400

A ranked move was submitted out of turn

hash_required

400

A ranked move is missing a state hash. Once any move in a match has sent an after hash, every later move must send both the before and after hashes. See Ranked matchmaking.

Payouts

Code

Status

Endpoint

When

email_required

400

POST /v1/me/connect/onboard (dashboard)

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

self

400

Trying to friend, follow, block, report or gift coins to yourself

friend_limit

409

Either player is already at the 1,000-friend maximum. The message says whether it's you or them.

friend_request_limit

409

You already have 100 outgoing friend requests waiting. Cancel a few, or wait for answers.

not_friends

400

Coin gifts only go to friends

insufficient

400

Not enough coins (platform shop, gifts)

claimed

400

Daily reward or season tier already claimed

not_reached

400

Season tier not reached yet

bad_tier

400

No such season tier

bad_item, owned, not_for_sale, not_owned

400

Platform cosmetics shop problems

no_request

400

No pending friend request from that player

comment_flagged, review_flagged

400

A comment or review was blocked by the content filter

too_many_collections, too_many_items

400

Over 100 collections, or over 500 games in one collection

bad_pin

400

That game can't be pinned to a profile

email_taken

409

The email is already used by another account

identity_already_linked

409

That sign-in provider is linked to a different account

no_email, bad_token

400

Email verification problems

gone

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 /v1/ request, whatever the endpoint and whether authenticated or not

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)

POST /v1/upload/init and POST /v1/games/:id/versions (one shared allowance)

Account

60 / hour

POST /v1/games/:id/media (covers, loading screens, screenshots)

Client IP

10 / hour

POST /v1/me/keys (creating API keys)

Account

20 / hour

POST /v1/dev/tournaments

Account

120 / minute

GET /v1/dev/games/:id/analytics/events (raw event export, up to 1,000 rows a page)

Account

5 / day

POST /v1/me/games/:id/versions/:versionId/appeal

Account

5 / day

POST /v1/dmca/counter-notices

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 (GameSDK.awardXp)

Player + game

60 / minute

Achievement unlocks (GameSDK.unlockAchievement)

Player + game

30 / minute

Score and replay submissions (GameSDK.submitScore)

Player + game

120 / minute

Cloud-save writes (GameSDK.saveData)

Player + game

60 / minute

In-game store purchases (GameSDK.economy.purchase)

Player

30 / hour

Friend requests (GameSDK.social.addFriend)

Player

90 / minute

Ranked queue joins (GameSDK.ranked.queue)

Player

20 / 10 seconds

Ranked polling: the queue and match reads, and rating lookups (GameSDK.ranked.poll)

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-After is 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/init or games/:id/versions call 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. nextCursor comes back null as soon as the next page would cross that line, so a loop that follows nextCursor always 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 uploadUrl

Idempotency-Key header

128 characters (longer values are cut)

POST /v1/games

Page size

1–48 (default 24)

GET /v1/games

Pagination depth

About 10,000 items (cursor_out_of_range past it)

Offset-cursor lists — see Pagination and cursors

Export page size

1–1,000 (default 500)

GET /v1/dev/games/:id/analytics/events

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 (connectHosts)

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

title

1–120 characters

description, instructions

4,000 characters each

tagline

160 characters

longDescription

8,000 characters

faq

20 entries; question 300 characters, answer 2,000

websites

5 URLs, each http(s)://, 500 characters max

tags

5 per game, from GET /v1/tags; slugs up to 24 characters

aiDisclosure

2,000 characters

Version changelog

2,000 characters

Appeal note

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 https://, max 500 characters

Store items

200 per game; sku 1–64 characters (letters, digits, _ . - :); name 80; description 300; priceCoins 0–1,000,000. kind is durable or consumable and locks once any player owns the SKU (kind_locked). Coins are the platform's virtual currency, not real money.

Tournaments

name 1–120 characters; description 1,000; prizePoolCoins 0–1,000,000; maxWinners 1–50

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, _ . - $ : and spaces)

Analytics event properties

50 keys, under 4,000 bytes as JSON

Friends

1,000 per player; 100 open outgoing requests (friend_limit, friend_request_limit)

Reported score

Scores above 1,000,000,000,000 are rejected as anomalies

Item

Lifetime

Play link ticket (playUrl ?t=…, preview links)

15 minutes

API keys

No expiry; valid until revoked

See also

Updated