Account linking
A player’s identity is recognized by player.id; sign-in methods are the paths linked to this identity. This page describes a guest’s move to a permanent account, linking and unlinking a Google identity, listing linked identities, and what happens in conflict cases.
Note
Open Play does not merge accounts. If an identity is linked to another player, the request is rejected; the progress of two players is never combined into one account by any means. The only exception is Google’s automatic linking by email rule; even that prevents a new player from being created and adds the identity to the existing account.
From guest to permanent account (upgrade)
A guest player moves to a permanent account in two ways; in both, player.id does not change, the guest secret is retired, and a new session is opened:
| Path | Call | Details |
|---|---|---|
| Email and password | POST /v1/auth/guest/upgrade/email · TS upgradeGuest · Unity UpgradeGuestAsync | Email and password |
POST /v1/auth/link/google + callback · TS linkOAuth + completeOAuth | Sign in with Google |
Upgrade is for guest players only: for a player who is not a guest, email upgrade gives 422. Email/password cannot be added later to an account that was created with Google or is already registered.
Listing linked identities
GET /v1/auth/identities
x-crx-env: <environment-id>
{
"player_id": "0192…",
"identities": [ { "provider": "google", "linked_at": "2026-11-03T10:00:00+00:00" } ]
}
- A session is required (otherwise
401). The response carriesCache-Control: private, no-store. - Only provider identities are listed (
google, …); email/password sign-in is not listed. The provider’s account ID (sub), email, and tokens are not in the response. - TypeScript:
session.listIdentities()returns{ playerId, identities: [{ provider, linkedAt }] }(camelCase); the response is strictly validated, an unexpected shape becomesCrxError(Contract)and does not drop the session.
This endpoint is enough to show “Link Google” or “Google linked” on your “My account” screen.
Linking an identity
POST /v1/auth/link/{provider} starts an OAuth flow for the player in the session and returns {url, state}; the player is redirected to url, and on return the callback completes it. Rules:
- One identity per provider: if this provider is already linked on the player, starting gives
422(unprocessable: provider_already_linked). - Session binding: the callback must come with the session of the player who started the link; a callback with no session or with another player’s session gets
403. - Identity on another player: if the Google identity is linked to another player, the callback gives
409(already exists: account_already_linked); the guest progress is left untouched. - A verified email is not required; email-less accounts can also link.
- Start and callback rates: 30 / 60 s and 60 / 60 s per IP.
Unlinking an identity
POST /v1/auth/unlink/google
x-crx-env: <environment-id>
x-csrf-token: <csrf-token>
204. A session is required. Errors:
| Status | Meaning |
|---|---|
400 | The last remaining provider identity cannot be unlinked (so the account is not locked out) |
404 | There is no identity linked to this provider |
Note
Because a player today usually has a single provider identity, the unlink call in practice mostly gets
400. The endpoint is in place to be used when a second provider is added. There is no SDK helper for unlink; use raw REST.
Conflict table
| Case | Result |
|---|---|
| A guest tries to upgrade with an email whose address is registered to another account | 409; the guest session is not damaged |
| A guest links to Google and the Google identity is on another player | 409 account_already_linked |
| A guest links to Google and the email Google verified matches another player’s email | 409 account_already_linked (no merge) |
| A registered player tries to link the same provider a second time | 422 provider_already_linked |
| A new Google identity matches the email of an existing email/password account (Google vouches) | The identity is automatically linked to that account, and the player enters that account |
| Linking to a banned account | Rejected (403) |