Skip to main content

Error codes

A single reference for every error an integrator can receive, with remediation. There are two channels: GraphQL errors (Management API and Game API) and realtime/UDP errors (the Game API UDP-proxy and the native Replication API).

GraphQL errors

GraphQL responses carry errors in the top-level errors array. Each entry has a message, a path, and an extensions object you can branch on programmatically. Beyond code, errors carry an extensions.remediation hint and — for permission failures — an extensions.requiredPermission:

{
"errors": [
{
"message": "Missing org permission 'manage_billing'",
"path": ["setAppBudget"],
"extensions": {
"code": "FORBIDDEN",
"httpStatus": 403,
"requiredPermission": "manage_billing",
"remediation": "Your token lacks the permission this operation requires. The field description (and its @requiresPermission directive) names the required permission; use a token/role that holds it."
}
}
],
"data": null
}
extensions.codeMeaningRemediation
UNAUTHENTICATEDNo bearer token, or it is invalid/expired.For Management API calls, sign in again (passwordless: magic link, social, or devLogin) for the session token — see Sign in (passwordless). For the Game API + realtime subscriptions, send an app-scoped token (mintAppToken / portal flow) as the Bearer (and in the ws connection_init payload) — the session token is rejected there.
FORBIDDENAuthenticated, but the token lacks the required permission for this field.Each operation's description and its @requiresPermission directive name the permission; extensions.requiredPermission carries the key. Use a token/role that holds it.
SCOPE_MISSINGThe token is scoped to a different org/app than the request targets.Use a token minted for the requested org/app, or a full-scope org token.
BAD_USER_INPUTAn argument failed validation (wrong type, out of range, missing required field).Check the argument descriptions in the SDL; BigInt must be a string, enums must be exact names.
BAD_REQUESTThe request was rejected by the resolver (e.g. a disabled feature, a precondition not met).Read message; it states the precondition.
GRAPHQL_VALIDATION_FAILEDThe query/mutation document is invalid against the schema.Validate against the published SDL or introspection.
NOT_FOUNDThe referenced entity does not exist or is not visible to you.Verify the ID and that your token can see it.
CONFLICTThe request conflicts with current state (incl. an idempotency key still processing).Refetch and retry if appropriate.
IDEMPOTENCY_CONFLICTAn idempotencyKey was reused with different request parameters.Use a new key, or resend the byte-identical request to replay the first result. See pagination/idempotency notes.
RATE_LIMITEDA rate/usage limit was exceeded.Back off and retry with exponential backoff.
INTERNAL_SERVER_ERRORUnexpected server error.Safe to retry idempotent reads; do not blind-retry non-idempotent mutations (send an idempotencyKey instead).

requiredPermission and the directive. Permission-gated fields carry a machine-readable @requiresPermission(scope:, permission:, scopeArg:) directive in the SDL/introspection, so an agent can plan calls without parsing prose. On a FORBIDDEN/SCOPE_MISSING error, extensions.requiredPermission echoes the missing key.

Idempotency. Economy-sensitive and destructive mutations accept an optional idempotencyKey (on the input object or as a top-level argument). Replaying with the same key and identical parameters returns the first result instead of re-applying; a different payload under the same key returns IDEMPOTENCY_CONFLICT. Keys expire after 24h.

Partial failures: GraphQL can return both data and errors in one response — a nullable field may resolve to null with a corresponding errors entry while the rest of data is populated. Always inspect errors even when data is present.

The portal browser handoff has a consent gate for untrusted apps. Minting a portal authorization code (createPortalAuthorizationCode) for an untrusted app the user has not authorized fails with a FORBIDDEN error whose message is prefixed CONSENT_REQUIRED:

{
"errors": [
{
"message": "CONSENT_REQUIRED: the user has not authorized this app. Call authorizeApp first.",
"path": ["createPortalAuthorizationCode"],
"extensions": { "code": "FORBIDDEN", "httpStatus": 403 }
}
],
"data": null
}

CONSENT_REQUIRED is not a distinct extensions.code — it is a FORBIDDEN whose message carries the marker. Resolve it on the Overworld by checking portalConsent(appId) { consentRequired } and recording approval with authorizeApp(input:{ appId }) before retrying. Trusted apps (the Overworld is app 1) skip consent entirely. CrowdyJS detects this proactively: client.portal.handleAuthorizeRequest throws PortalConsentRequiredError (pass grantConsent: true to approve). The requested redirectUri must also be in the app's redirectUris allow-list, or the call is rejected (BAD_REQUEST). See Portals & app-scoped tokens.

Realtime / UDP errors

The Game API UDP-proxy surfaces server-side spatial errors asynchronously on the udpNotifications subscription, not as GraphQL errors. A spatial-send mutation returning true only means the datagram was accepted for sending.

GenericErrorResponse.errorCode (UdpErrorCode)

Correlate to the request that caused it by sequenceNumber.

CodeMeaningRemediation
NO_ERRORSuccess.
UNKNOWN_ERRORUnspecified server error.Retry; report if persistent.
INVALID_TOKENThe token is malformed, revoked, or not a valid app-scoped gameplay token.Mint a fresh app-scoped token (mintAppToken / exchangePortalCode, or refreshAppToken for the same app); the identity session token is not valid here.
APP_NOT_FOUNDNo app matches the supplied appId.Verify appId.
UNAUTHORIZEDMissing the runtime/grid permission for this action.May be transient on first entry to a new region while grid permissions load — retry shortly; otherwise obtain the permission.
GAME_TOKEN_WRONG_SIZEThe token is not the expected length.Send the exact 64-character app-scoped token from mintAppToken / exchangePortalCode (no trimming/re-encoding).
INVALID_REQUESTThe message was malformed or failed validation.Check the message shape / arguments.
INVALID_APP_IDappId was missing, zero, or invalid — or the token is not scoped to the packet's app (app-scoped token confinement).Supply a valid appId, and use the token minted for that app.
USER_NOT_AUTHENTICATEDNo session on the server for this client.Open the UDP proxy (connectUdpProxy) or complete the native token handshake first.
TOKEN_EXPIREDThe app-scoped gameplay token's TTL elapsed mid-session.Refresh the app token (same app: refreshAppToken) before it lapses, or re-portal through the Overworld for a fresh one, then re-authorize the session.

The full enum (including login-validation codes that never appear on the UDP wire) is in the Game API SDL as UdpErrorCode, each value documented. The native-UDP view of these codes is in Operations and Wire formats.

RealtimeConnectionEvent.code

Emitted when the realtime session itself cannot be established (distinct from a per-message error):

CodeMeaningRemediation
AUTH_REQUIREDThe subscription opened without a valid bearer token.Send the token in the connection_init payload.
APP_ID_REQUIREDThe subscription was app-agnostic.Scope the subscription to one appId (run one client per app).
APP_TOKEN_REQUIREDThe subscription presented an identity session token, not an app-scoped gameplay token.Obtain a token scoped to this app (portal in via the Overworld, or mintAppToken) and use it for gameplay.
APP_SCOPE_MISMATCHThe token is scoped to a different app than the subscription's appId.Use the token minted for the app you are subscribing to.
UDP_PROXY_CONNECTION_FAILEDThe server could not open the upstream UDP proxy session.Inspect retryable; back off and retry if true.

Native UDP: silent drops

On the native Replication API, some failures produce no reply at all — a missing or invalid HMAC, an unknown token, or an unparseable packet is dropped without a NAK. Do not treat silence as a network black hole. If you sent an authenticated message and receive neither a notification nor a GENERIC_ERROR_MESSAGE, re-check the HMAC and token before assuming packet loss. (This does not apply to the GraphQL UDP-proxy path, which authenticates at connect time.) See Troubleshooting.