View as Markdown

Error reference

This page is a reference for the errors you may encounter when working with Nakama, so you can identify what an error means, whether it’s expected, and what to check next.

Nakama exposes its API over both gRPC and HTTP. Clients receive errors from two layers:

  • Transport and networking errors from the gRPC/HTTP layer, such as a canceled request or a timed-out connection.
  • Application errors from the server, carrying a gRPC status code and a human-readable message.

Server framework code (Go, TypeScript, and Lua modules) also returns a set of predefined runtime error values, listed at the end of this page.

When you’re debugging, the gRPC status code is the stable signal to branch on, but the detail you need is often in the server log rather than the client response. Several errors below return a generic message to the client while recording the specific reason in the log.

gRPC status codes and HTTP mappings #

Every Nakama API error carries a gRPC status code. When a request arrives over HTTP, Nakama maps that code to an HTTP status code. The mapping is the gRPC-gateway default and is stable across Nakama releases.

gRPC codeNumberHTTP statusMeaning
OK0200 OKThe request succeeded.
Canceled1499 Client Closed RequestThe caller canceled the request before it completed.
Unknown2500 Internal Server ErrorAn unknown error, often an unhandled exception on the server.
InvalidArgument3400 Bad RequestA field in the request was missing or malformed.
DeadlineExceeded4504 Gateway TimeoutThe request ran longer than its allotted timeout.
NotFound5404 Not FoundThe requested resource doesn’t exist.
AlreadyExists6409 ConflictThe resource the request tried to create already exists.
PermissionDenied7403 ForbiddenThe caller isn’t authorized to perform the operation.
ResourceExhausted8429 Too Many RequestsA resource quota or rate limit was reached.
FailedPrecondition9400 Bad RequestThe system isn’t in the state required to run the operation.
Aborted10409 ConflictThe operation was aborted, typically due to a concurrency conflict.
OutOfRange11400 Bad RequestThe operation was attempted past the valid range.
Unimplemented12501 Not ImplementedThe operation isn’t implemented or supported.
Internal13500 Internal Server ErrorAn unexpected internal server error.
Unavailable14503 Service UnavailableThe service is currently unavailable, often transient.
DataLoss15500 Internal Server ErrorUnrecoverable data loss or corruption.
Unauthenticated16401 UnauthorizedThe request lacks valid authentication credentials.

The status code tells you the category of problem. The codes you see most often from application logic are InvalidArgument (a malformed or missing field in your request), NotFound (the requested resource doesn’t exist), and Internal (an unexpected server-side failure).

Per-endpoint error messages #

Alongside the status code, most API errors carry a human-readable message describing what specifically went wrong. A bad pagination value, for example, returns Invalid limit - limit must be between 1 and 100.

These messages are defined per endpoint rather than centrally. They live in the api_*.go files of the Nakama server source, such as server/api_storage.go and server/api_account.go, and there are several hundred of them across the API. Because each call site defines its own wording, the same category of problem can be phrased differently by different endpoints.

Treat these messages as diagnostic text for people, not as a stable API contract. Branch on the gRPC status code instead, which is stable across releases. To trace a message back to its origin, search the server/api_*.go files for the message text.

Some operations don’t expose a distinct status code for every outcome. When a client joins a match that no longer exists, for example, the reliable signal is the match not found message rather than a dedicated code. Where an operation lacks a specific code for a case you need to detect, match on the documented message and treat the status code as the coarse category.

Common transport and networking errors #

These errors come from the network or the gRPC layer rather than from Nakama’s application logic. Many are expected under normal operating conditions and don’t indicate a bug in your integration or in the server.

Context canceled #

The context canceled error (gRPC Canceled, code 1) means the caller ended the request before it completed. This is one of the most common errors, and it’s usually expected: a client closed the connection, navigated away, or canceled the operation while the request was in flight. Over gRPC the full text is rpc error: code = Canceled desc = context canceled.

A frequent and benign form is a storage read logged as Could not read storage objects. with "error": "context canceled". This means the client disconnected before its database query returned. It’s safe to ignore unless it coincides with database performance problems.

What to check: confirm the affected requests are ones a client would abandon, such as a screen the player navigated away from or a request behind a flaky connection. Investigate only when cancellation hits requests that should always run to completion, or when you see a sustained spike, which points to database pressure or a client that cancels too aggressively.

Context deadline exceeded #

The context deadline exceeded error (gRPC DeadlineExceeded, code 4) means an operation ran longer than its allotted timeout. Like context cancellation, it’s common and often expected.

A frequent source is an outbound HTTP call from your own runtime code to an external service, such as Satori, an in-app purchase validation endpoint, or a third-party API. These surface as context deadline exceeded (Client.Timeout exceeded while awaiting headers), which means your HTTP client’s own timeout elapsed while waiting on the other service. The fix is usually to raise that client’s timeout, not a Nakama setting.

What to check: identify which operation is timing out. If it’s an outbound call from your module, raise that HTTP client’s timeout and check the external service’s latency. If it’s a database operation, check query duration and load such as IOPS and hot rows. Tune Nakama timeouts only once you’ve ruled out a slow dependency.

Network I/O timeout #

An I/O timeout (for example, read tcp ... i/o timeout) means a network read or write didn’t complete in time. This is most often caused by an unreliable client connection (a device on a poor mobile network, for example) rather than a problem on the server side.

Treat isolated I/O timeouts as a symptom of network conditions. Investigate only if they’re widespread across many clients at once, which can indicate a server or infrastructure issue.

SSL handshake failure #

An SSL or TLS handshake failure means the client and server couldn’t establish a secure connection. This is typically a TLS configuration or connectivity issue between the two parties, such as a certificate problem, a protocol mismatch, or a proxy interfering with the connection. It isn’t an application error.

The exact message depends on the client platform. Common forms are The SSL connection could not be established, Curl error 60: Cert verify failed, an SSL CA certificate error, and a TrustFailure or Handshake failed exception. A connection that works on one network but fails on another, for example failing on home broadband but working on mobile data, points to the network path (DNS, IPv6, a proxy, or an ISP) rather than the server.

What to check: confirm the certificate chain is trusted on the failing platform, that client and server agree on a TLS version, and whether the failure is specific to one network. A sudden onset across many clients can follow certificate rotation on a dependency such as Satori.

Storage version rejection #

A conditional write whose version check fails returns gRPC InvalidArgument (code 3, HTTP 400) with the message Storage write rejected. The server framework surfaces it as ErrStorageRejectedVersion. This is the expected outcome when another writer updated the object first, and it’s how Nakama prevents conflicting concurrent writes.

The server returns the same code and message when it rejects a write for insufficient permissions (ErrStorageRejectedPermission), so the response alone doesn’t tell you which of the two applied. A conditional delete that fails its version check returns Storage delete rejected. with the same code.

When you use Hiro, the same rejection appears in server logs as an nk.MultiUpdate error carrying Storage write rejected - version check failed., from the economy, inventory, or achievements systems. It means two operations wrote the same object concurrently, for example an achievement claim and an achievement update running at once, or a client retry replaying a write with a stale version.

What to check: look for two writes to the same object overlapping in time. Common sources are concurrent Hiro operations on one user, an offline queue replaying writes on reconnect, and account creation or linking hooks that edit a wallet which already exists. To reduce it, batch updates to the same object into one call, sequence operations that touch the same object, and retry a rejected write with jitter after re-reading the current version.

For how conditional writes work and how to handle a rejected version, see Conditional writes .

Socket heartbeat disconnects #

Nakama keeps realtime socket connections alive with a heartbeat. The server sends a WebSocket ping every socket.ping_period_ms (15 seconds by default) and expects a pong back within socket.pong_wait_ms (25 seconds by default). If no pong or other message arrives in that window, the server treats the connection as dead and closes it.

The most common cause of an unexpected heartbeat disconnect is a blocked client rather than a network fault. A WebSocket library answers pings automatically, but only while the client keeps servicing its network loop. Long blocking work on that same thread, such as loading assets or running a synchronous scene transition, holds the pong back until the work finishes. If that takes longer than socket.pong_wait_ms, the server has already closed the connection.

To avoid this, keep servicing the socket during long operations, move blocking work off the thread that drives the socket, or split it into chunks that yield frequently. Raising socket.pong_wait_ms buys headroom but doesn’t remove the underlying stall. Always handle the disconnect event and reconnect, because connections also drop for ordinary network reasons.

A client that sends messages regularly doesn’t need a ping to prove it’s alive. The server skips one when the client sent at least socket.ping_backoff_threshold messages (20 by default) during the ping period.

Social authentication errors #

A failed social login returns a generic message to the client rather than the reason the identity provider gave. Authenticating against Google, Apple, Facebook, Game Center, or Steam returns gRPC Unauthenticated (HTTP 401) with a message that names only the provider, such as Could not authenticate Google profile.

The specific reason comes from the provider, and Nakama writes it to the server log instead of returning it to the client. Typical reasons include an invalid or expired ID token, an unexpected issuer claim, and a failed certificate lookup. Nakama doesn’t generate these reasons, so repeated failures usually point to provider-side credentials or configuration rather than to Nakama.

Debug a social authentication failure from the server log entry recorded with the request. The client message alone can’t tell you which reason applied.

Runtime error values #

The server framework defines a fixed set of error values in nakama-common. Runtime operations return these, so you can match against them in your Go, TypeScript, and Lua modules. The common ones you’re likely to act on, such as storage version rejection, are explained in the sections above; the table below is the complete list.

The messages are intentionally terse and often restate the error name, so the value itself rarely tells you what to do next. Match the error rather than its message text (in Go, with errors.Is), then follow the What to do next column, which explains when each error fires and how to handle it. For the fuller flow, see the concept guide for the system the error belongs to.

ErrorMessageWhat to do next
ErrCannotDecodeParamserror creating match: cannot decode paramsNakama couldn’t decode the stored match parameters. Make sure the params you created the match with are serializable values.
ErrCannotEncodeParamserror creating match: cannot encode paramsNakama couldn’t encode the parameters passed to match create. Pass only serializable values in the params map.
ErrChannelCursorInvalidinvalid channel cursorThe pagination cursor doesn’t match this channel query. Pass back the cursor from the previous page unchanged, or omit it to start from the first page.
ErrChannelGroupNotFoundgroup not foundThe group behind this channel doesn’t exist. Confirm the group ID and that the group hasn’t been deleted.
ErrChannelIDInvalidinvalid channel idThe channel ID is malformed. Build it from a valid channel target instead of composing the string by hand.
ErrDeferredBroadcastFulltoo many deferred message broadcasts per tickToo many messages were queued for broadcast in a single match tick. Send fewer messages per tick, or spread them across ticks.
ErrFriendInvalidCursorfriend cursor invalidThe friend list pagination cursor is invalid. Reuse the cursor from the previous page, or omit it to start over.
ErrGracePeriodExpiredgrace period expiredA graceful shutdown’s grace period elapsed before this operation finished. Expected during shutdown or redeploys: make long operations idempotent and safe to retry so the client can repeat them after reconnecting.
ErrGroupCreatorInvalidgroup creator user ID not validThe creator user ID isn’t a valid UUID. Pass the ID of an existing user.
ErrGroupFullgroup is fullThe group is at its member limit. Handle it as full, or raise its max count.
ErrGroupLastSuperadminuser is last group superadminThe last superadmin can’t leave or be demoted. Promote another member to superadmin first.
ErrGroupNameInUsegroup name in useAnother group already uses this name. Pick a unique name.
ErrGroupNoUpdateOpsno group updatesThe update request didn’t change any fields. Include at least one field to update.
ErrGroupNotFoundgroup not foundNo group exists with this ID. Check the ID and that the group hasn’t been deleted.
ErrGroupNotUpdatedgroup not updatedThe group update didn’t apply. Confirm the group exists and that the field values are valid.
ErrGroupPermissionDeniedgroup permission deniedThe caller lacks permission for this group operation. Check their group role, since only admins and superadmins can manage members and settings.
ErrGroupUserInvalidCursorgroup user cursor invalidThe group members pagination cursor is invalid. Reuse the cursor from the previous page, or omit it to start over.
ErrGroupUserNotFounduser not foundThe target user isn’t a member of this group. Check the user ID and membership.
ErrInvalidChannelTargetInvalid channel targetThe channel target is empty or malformed. Pass a user ID, group ID, or room name that matches the channel type you’re joining.
ErrInvalidChannelTypeInvalid channel typeThe channel type isn’t a supported value. Use a room, direct message, or group channel type.
ErrLeaderboardNotFoundleaderboard not foundNo leaderboard exists with this ID. Create it at server startup, or check the ID for typos.
ErrMatchBusymatch busyThe match’s processing queue is full, so it can’t take this call right now. Retry shortly, and reduce how often you call into a single match.
ErrMatchIdInvalidmatch id invalidThe match ID is malformed. Use an ID returned by Nakama rather than composing one by hand.
ErrMatchLabelTooLongmatch label too long, must be 0-2048 bytesThe match label exceeds 2048 bytes. Shorten it.
ErrMatchNotFoundmatch not foundNo match exists with this ID. It may have ended or never started. On reconnect, treat this as the match no longer being available and route the player elsewhere.
ErrMatchStateFailedmatch did not return stateA match handler callback didn’t return state. Return the state value from every match loop callback, including when nothing changed.
ErrMatchmakerDeletematchmaker delete errorThe matchmaker couldn’t remove the ticket. Confirm the ticket ID still exists, then retry.
ErrMatchmakerDuplicateSessionmatchmaker duplicate sessionThe session already has an active ticket in this matchmaker. Cancel or reuse the existing ticket instead of adding another.
ErrMatchmakerIndexmatchmaker index errorAn internal matchmaker indexing error occurred. Retry the request, and check the server logs if it persists.
ErrMatchmakerNotAvailablematchmaker not availableThe matchmaker isn’t ready to serve requests, usually briefly during startup or a cluster change. Retry after a short delay, and check cluster health if it continues.
ErrMatchmakerQueryInvalidmatchmaker query invalidThe matchmaker query is malformed. Check the query syntax and the properties it references.
ErrMatchmakerTicketNotFoundmatchmaker ticket not foundNo matchmaker ticket matches this ID for the session. Check the ticket ID, and note it may already have matched or been canceled.
ErrMatchmakerTooManyTicketsmatchmaker too many ticketsThe session is over its matchmaker ticket limit. Cancel unused tickets before adding new ones.
ErrPartyAcceptRequestparty could not accept requestThe join request couldn’t be accepted, usually because the party filled or the request was withdrawn. Re-check the party state before retrying.
ErrPartyClosedparty closedThe party is closed to open joins. Only invited users can join, so send an invite or open the party.
ErrPartyFullparty fullThe party is at its member limit. Handle it as full, or create the party with a larger max size.
ErrPartyJoinRequestAlreadyMemberparty join request already memberThe user is already in the party, so no join request is needed.
ErrPartyJoinRequestDuplicateparty join request duplicateA join request from this user is already pending. Wait for the leader to respond rather than resending.
ErrPartyJoinRequestsFullparty join requests fullThe party’s pending join requests are at capacity. Have the leader accept or reject some before more can arrive.
ErrPartyLabelTooLongparty label too longThe party label exceeds the size limit. Shorten it.
ErrPartyNotLeaderparty leader onlyThis operation is leader-only. Perform it as the party leader.
ErrPartyNotMemberparty member not foundThe target user isn’t in the party. Check the user ID and current membership.
ErrPartyNotRequestparty join request not foundNo pending join request matches this user. It may have been accepted, rejected, or withdrawn already.
ErrPartyRemoveparty could not removeThe member couldn’t be removed. Confirm they’re still in the party and that you’re the leader.
ErrPartyRemoveSelfparty cannot remove selfA leader can’t remove themselves. Promote another member to leader first, or close the party.
ErrSatoriConfigurationInvalidsatori configuration is invalidSatori isn’t configured correctly. Check the Satori URL and API key in your Nakama configuration.
ErrStorageRejectedPermissionStorage write rejected - permission denied.The caller lacks write permission for this object. Check the object’s write permission and that the caller owns it, or perform the write as the system user from server code.
ErrStorageRejectedVersionStorage write rejected - version check failed.Another writer updated the object first, so a conditional write lost the version check. Re-read the object, take its new version, and retry the write with jitter. See Conditional writes .
ErrStorageWriteExhaustedRetriesStorage write retries exhausted.Nakama retried a conditional write repeatedly and kept losing the version race. Reduce concurrent writes to the same object, or batch them into a single update call.
ErrTournamentAuthoritativetournament only allows authoritative submissionsThe tournament only accepts server-authoritative submissions. Submit the score from your server runtime code, not from the client.
ErrTournamentMaxSizeReachedtournament max size reachedThe tournament is full. Handle it as full, or raise its max size when you create it.
ErrTournamentNotFoundtournament not foundNo tournament exists with this ID. Create it before writing a record, or check the ID.
ErrTournamentOutsideDurationtournament outside of durationThe tournament isn’t active right now. Check its start time, duration, and reset schedule before submitting.
ErrTournamentWriteJoinRequiredrequired to join before writing tournament recordThe player must join the tournament before submitting a score. Call the join operation first.
ErrTournamentWriteMaxNumScoreReachedmax number score count reachedThe player used every submission allowed in the active period. Wait for the next reset, or raise the tournament’s max score count.
ErrUserGroupInvalidCursoruser group cursor invalidThe user’s groups pagination cursor is invalid. Reuse the cursor from the previous page, or omit it to start over.
ErrWalletLedgerInvalidCursorwallet ledger cursor invalidThe wallet ledger pagination cursor is invalid. Reuse the cursor from the previous page, or omit it to start over.