Sign in, manage API keys and service accounts, and decide who may do what. Exchange a password or an API key for a short-lived token with IssueToken, and use that token on every other call. Access is granted by binding roles to people and service accounts on an account, a project or a single resource; CheckPermissions tells you which actions a caller may take.
Registers a new user and signs them in, returning tokens as well as the user so no second call is needed.
Plany is invitation-only, so this is refused unless registration is open. An invited user signs in with the password they were emailed and then sets their own with ChangePassword.
emailstringRequiredpasswordstringRequiredPlaintext, hashed with Argon2id before storage and never persisted or logged. Length is checked but composition is not: length is what actually resists guessing, and composition rules mostly produce predictable substitutions.
display_namestringcreate_accountboolCreate an account owned by the new user, and grant them
roles/account.owner on it.
The common case: someone signing up to host a game server needs an account before they can create anything, and making that a second call means every client reimplements the same two-step dance.
account_display_namestringDisplay name for that account. Defaults to the user's own display name.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "email": "", "password": "" }' \ api.plany.cloud:443 plany.iam.v1.IamService/RegisterUser{ "user": "", "email": "", "account": "accounts/", "tokens": { "access_token": "", "refresh_token": "", "expire_time": "2026-10-04T12:00:00Z", "scopes": [ "" ], "principal": { "name": "", "type": "PRINCIPAL_TYPE_USER", "account": "accounts/", "display_name": "My server", "email": "", "scopes": [ "" ], "project_restrictions": [ "" ], "credential_id": "my-credential", "expire_time": "2026-10-04T12:00:00Z" }, "account_warning": "", "password_change_required": false, "email_verification_required": false }}Replaces a user's password, proving they hold the current one.
The other half of InviteUser: an invitee signs in with a token that can
do nothing but this, and gets a full one back. It is also the ordinary
change-your-password call for anybody else.
current_passwordstringRequiredThe password being replaced.
Required even for a credential that must change: the token an invitee holds was issued to whoever read the invitation email, and without this a forwarded message would be a full account takeover.
new_passwordstringRequiredgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "current_password": "", "new_password": "" }' \ api.plany.cloud:443 plany.iam.v1.IamService/ChangePassword{ "tokens": { "access_token": "", "refresh_token": "", "expire_time": "2026-10-04T12:00:00Z", "scopes": [ "" ], "principal": { "name": "", "type": "PRINCIPAL_TYPE_USER", "account": "accounts/", "display_name": "My server", "email": "", "scopes": [ "" ], "project_restrictions": [ "" ], "credential_id": "my-credential", "expire_time": "2026-10-04T12:00:00Z" }, "account_warning": "", "password_change_required": false, "email_verification_required": false }}Emails a single-use link that sets a new password.
Unauthenticated: it is for somebody who cannot sign in. It answers the same way whether or not the address has an account, so it cannot be used to find out which addresses do — the email is the only place the answer goes. Repeated requests for one address within a minute send one email.
emailstringRequiredgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "email": "" }' \ api.plany.cloud:443 plany.iam.v1.IamService/RequestPasswordReset{}Sets a new password with the token from a reset email, and signs in.
Unauthenticated, like IssueToken: the token is the credential. It works
once, for an hour, and only while the account's email is still the address
it was sent to. Every existing session is revoked, and holding the link
proves the inbox, so the address is marked confirmed too.
tokenstringRequiredFrom the reset link. Single-use, valid for an hour.
new_passwordstringRequiredAt least twelve characters, as everywhere else.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "token": "", "new_password": "" }' \ api.plany.cloud:443 plany.iam.v1.IamService/ResetPassword{ "tokens": { "access_token": "", "refresh_token": "", "expire_time": "2026-10-04T12:00:00Z", "scopes": [ "" ], "principal": { "name": "", "type": "PRINCIPAL_TYPE_USER", "account": "accounts/", "display_name": "My server", "email": "", "scopes": [ "" ], "project_restrictions": [ "" ], "credential_id": "my-credential", "expire_time": "2026-10-04T12:00:00Z" }, "account_warning": "", "password_change_required": false, "email_verification_required": false }, "email": ""}Sends the signed-in user a link that confirms their email address.
Registration sends the first one itself; this is "send it again". Until
the address is confirmed, a registered user's token carries a single scope
and is refused by every service, so this and VerifyEmail are the two
calls such a session exists to make.
No parameters.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ api.plany.cloud:443 plany.iam.v1.IamService/SendEmailVerification{}Confirms an email address with the token from a verification email.
Unauthenticated, because the link is often opened somewhere other than where the person registered — a phone, a different browser. It confirms the address and nothing else: it is not a sign-in link, so it returns no tokens. A session issued before confirmation is widened on its next refresh.
tokenstringRequiredFrom the verification link. Single-use, valid for a day.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "token": "" }' \ api.plany.cloud:443 plany.iam.v1.IamService/VerifyEmail{ "email": ""}Exchanges a credential — a password or an API key — for a short-lived token.
This is the one call that accepts a raw credential. Everything else on Plany takes the token it returns.
GRANT_TYPE_PASSWORDGRANT_TYPE_API_KEYGRANT_TYPE_REFRESH_TOKENGRANT_TYPE_SERVICE_ACCOUNT_ASSERTIONGRANT_TYPE_OIDC_ASSERTIONcredentialstringThe credential, interpreted according to grant_type: the API key, the refresh token, or the signed assertion. Unset for PASSWORD, which uses the email/password fields.
emailstringpasswordstringmfa_codestringOne-time code, when the account has multi-factor authentication enabled.
scopesrepeated stringNarrow the issued token below what the credential could otherwise do. Requesting a scope the credential lacks fails rather than silently downgrading.
accountstringBind the token to one account, for a user belonging to several.
Optional, and resolved automatically when the user holds a role on exactly one account — the ordinary case. With several, sign-in fails with FAILED_PRECONDITION naming them rather than guessing, because every service decides tenancy by comparing against this claim and a wrong guess silently scopes the session to the wrong tenant.
Checked, not trusted: naming an account the user holds no role on is refused.
requested_lifetimeDurationRequested lifetime, capped by policy. Zero takes the default.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "grant_type": "GRANT_TYPE_PASSWORD" }' \ api.plany.cloud:443 plany.iam.v1.IamService/IssueToken{ "access_token": "", "refresh_token": "", "expire_time": "2026-10-04T12:00:00Z", "scopes": [ "" ], "principal": { "name": "", "type": "PRINCIPAL_TYPE_USER", "account": "accounts/", "display_name": "My server", "email": "", "scopes": [ "" ], "project_restrictions": [ "" ], "credential_id": "my-credential", "expire_time": "2026-10-04T12:00:00Z" }, "account_warning": "", "password_change_required": false, "email_verification_required": false}Revokes a token or an entire credential's token family before expiry.
tokenstringThe token to revoke. Mutually exclusive with credential_name.
credential_namestringRevoke every unexpired token minted from this credential, e.g.
accounts/a/apiKeys/k. The blast radius control for a leaked key.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ api.plany.cloud:443 plany.iam.v1.IamService/RevokeToken{}Returns the public keys that verify Plany-issued tokens, as a JSON Web Key Set. Keys are published before they are used, so a cached copy never misses a rotation.
No parameters.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ api.plany.cloud:443 plany.iam.v1.IamService/GetPublicKeys{ "keys": [ { "kid": "", "kty": "", "alg": "", "use": "", "crv": "", "x": "", "y": "", "n": "", "e": "" } ], "cache_duration": "300s"}Decides whether a principal may perform permissions on a resource.
Returns the subset of permissions that are allowed rather than a bare
boolean, so a UI can grey out actions with one round trip.
resourcestringRequiredResource being acted on, e.g. projects/p1 or
projects/p1/locations/eu-west-2/instances/i1.
permissionsrepeated stringRequiredPermissions to test, in service.resource.verb form.
principalPrincipalPrincipal to test. Omitted means the caller, which is the normal case.
Supplying one requires the caller to hold iam.policies.check on the
resource, so this cannot be used to enumerate others' access.
namestringRequiredResource name of the underlying identity:
users/{user}
accounts/{account}/serviceAccounts/{service_account}
accounts/{account}/apiKeys/{api_key}
services/{service}
PRINCIPAL_TYPE_USERPRINCIPAL_TYPE_SERVICE_ACCOUNTPRINCIPAL_TYPE_API_KEYPRINCIPAL_TYPE_SERVICEPRINCIPAL_TYPE_OPERATORaccountstringAccount this principal acts on behalf of, accounts/{account}. Empty only
for PRINCIPAL_TYPE_SERVICE. All spend and quota consumption is attributed
here.
display_namestringDisplay name for audit logs and UIs. Never used for authorization.
emailstringVerified email, for USER principals only.
scopesrepeated stringScopes the presented credential carries, which upper-bound what the
principal may do in this call regardless of its granted roles. An API key
restricted to microvm.instances.read cannot delete an instance even if
its owner is an account admin.
project_restrictionsrepeated stringProjects the credential is restricted to, as projects/{project} names.
Empty means every project in the account, subject to role checks.
credential_idstringOpaque id of the credential used, for audit correlation and revocation.
For a JWT this is the jti claim.
expire_timeTimestampWhen the presented credential stops being valid. Calls made after this are refused.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "resource": "", "permissions": [] }' \ api.plany.cloud:443 plany.iam.v1.IamService/CheckPermissions{ "permissions": [ "" ]}grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "resource": "" }' \ api.plany.cloud:443 plany.iam.v1.IamService/GetIamPolicy{ "resource": "", "bindings": [ { "role": "roles/my-role", "members": [ "" ] } ], "etag": "W/\"1\""}resourcestringRequiredresourcestringRequiredResource the policy attaches to, accounts/{account} or
projects/{project}.
bindingsrepeated BindingrolestringRequiredroles/{role}.
membersrepeated stringRequiredPrincipal names the role is granted to. Also accepts the special member
allAccountMembers, which covers every member of the owning account.
etagstringOpaque concurrency token. SetIamPolicy fails with ABORTED if it does not match the stored value, which makes read-modify-write safe without holding a lock.
update_maskFieldMaskgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "resource": "", "policy": { "resource": "" } }' \ api.plany.cloud:443 plany.iam.v1.IamService/SetIamPolicy{ "resource": "", "bindings": [ { "role": "roles/my-role", "members": [ "" ] } ], "etag": "W/\"1\""}scopeRole.ScopeReturn only roles bindable at this scope.
SCOPE_ACCOUNTSCOPE_PROJECTSCOPE_BOTHpage_sizeint32page_tokenstringgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "page_size": 50 }' \ api.plany.cloud:443 plany.iam.v1.IamService/ListRoles{ "roles": [ { "name": "roles/my-role", "display_name": "My server", "description": "", "included_permissions": [ "" ], "scope": "SCOPE_ACCOUNT", "operator_only": false } ], "next_page_token": ""}Creates an API key. The plaintext key is returned only here and never again; the service stores a hash.
parentstringRequireddisplay_namestringRequirednamestringIdentifieraccounts/{account}/apiKeys/{api_key}.
scopesrepeated stringPermission scopes this key may exercise, upper-bounding the roles held by the identity it acts as. Empty means every scope that identity has, which callers should avoid for keys deployed to game servers.
project_restrictionsrepeated stringProjects this key may touch. Empty means all projects in the account.
allowed_ip_rangesrepeated stringCIDR ranges the key may be presented from. Empty means anywhere. Worth setting for a key baked into a fixed fleet.
subjectstringIdentity this key authenticates as. Usually a service account; a key acting as a user inherits that user's roles and is discouraged.
expire_timeTimestampWhen the key stops working. Empty means it never expires, which the API permits but the console warns about.
api_key_idstringClient-chosen id. Empty generates one.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "accounts/", "api_key": { "display_name": "My server" } }' \ api.plany.cloud:443 plany.iam.v1.IamService/CreateApiKey{ "api_key": { "name": "accounts//apiKeys/my-api-key", "display_name": "My server", "key_prefix": "", "state": "STATE_ACTIVE", "scopes": [ "" ], "project_restrictions": [ "projects/" ], "allowed_ip_ranges": [ "" ], "subject": "", "create_time": "2026-10-04T12:00:00Z", "expire_time": "2026-10-04T12:00:00Z", "last_use_time": "2026-10-04T12:00:00Z", "creator": "", "token_expire_time": "2026-10-04T12:00:00Z" }, "key_string": ""}grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "accounts//apiKeys/my-api-key" }' \ api.plany.cloud:443 plany.iam.v1.IamService/GetApiKey{ "name": "accounts//apiKeys/my-api-key", "display_name": "My server", "key_prefix": "", "state": "STATE_ACTIVE", "scopes": [ "" ], "project_restrictions": [ "projects/" ], "allowed_ip_ranges": [ "" ], "subject": "", "create_time": "2026-10-04T12:00:00Z", "expire_time": "2026-10-04T12:00:00Z", "last_use_time": "2026-10-04T12:00:00Z", "creator": "", "token_expire_time": "2026-10-04T12:00:00Z"}parentstringRequiredpage_sizeint32page_tokenstringfilterstringAIP-160 filter, e.g. state = "ACTIVE".
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "accounts/", "page_size": 50 }' \ api.plany.cloud:443 plany.iam.v1.IamService/ListApiKeys{ "api_keys": [ { "name": "accounts//apiKeys/my-api-key", "display_name": "My server", "key_prefix": "", "state": "STATE_ACTIVE", "scopes": [ "" ], "project_restrictions": [ "projects/" ], "allowed_ip_ranges": [ "" ], "subject": "", "create_time": "2026-10-04T12:00:00Z", "expire_time": "2026-10-04T12:00:00Z", "last_use_time": "2026-10-04T12:00:00Z", "creator": "", "token_expire_time": "2026-10-04T12:00:00Z" } ], "next_page_token": ""}display_namestringRequirednamestringIdentifieraccounts/{account}/apiKeys/{api_key}.
scopesrepeated stringPermission scopes this key may exercise, upper-bounding the roles held by the identity it acts as. Empty means every scope that identity has, which callers should avoid for keys deployed to game servers.
project_restrictionsrepeated stringProjects this key may touch. Empty means all projects in the account.
allowed_ip_rangesrepeated stringCIDR ranges the key may be presented from. Empty means anywhere. Worth setting for a key baked into a fixed fleet.
subjectstringIdentity this key authenticates as. Usually a service account; a key acting as a user inherits that user's roles and is discouraged.
expire_timeTimestampWhen the key stops working. Empty means it never expires, which the API permits but the console warns about.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "api_key": { "display_name": "My server" }, "update_mask": "display_name" }' \ api.plany.cloud:443 plany.iam.v1.IamService/UpdateApiKey{ "name": "accounts//apiKeys/my-api-key", "display_name": "My server", "key_prefix": "", "state": "STATE_ACTIVE", "scopes": [ "" ], "project_restrictions": [ "projects/" ], "allowed_ip_ranges": [ "" ], "subject": "", "create_time": "2026-10-04T12:00:00Z", "expire_time": "2026-10-04T12:00:00Z", "last_use_time": "2026-10-04T12:00:00Z", "creator": "", "token_expire_time": "2026-10-04T12:00:00Z"}Issues a replacement key while leaving the old one usable for a grace period, so a fleet can roll over without downtime.
namestringRequiredgrace_periodDurationHow long the old key keeps working. Zero takes the default of 24 hours.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "accounts//apiKeys/my-api-key" }' \ api.plany.cloud:443 plany.iam.v1.IamService/RotateApiKey{ "api_key": { "name": "accounts//apiKeys/my-api-key", "display_name": "My server", "key_prefix": "", "state": "STATE_ACTIVE", "scopes": [ "" ], "project_restrictions": [ "projects/" ], "allowed_ip_ranges": [ "" ], "subject": "", "create_time": "2026-10-04T12:00:00Z", "expire_time": "2026-10-04T12:00:00Z", "last_use_time": "2026-10-04T12:00:00Z", "creator": "", "token_expire_time": "2026-10-04T12:00:00Z" }, "key_string": ""}grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "accounts//apiKeys/my-api-key" }' \ api.plany.cloud:443 plany.iam.v1.IamService/DeleteApiKey{}parentstringRequirednamestringIdentifieraccounts/{account}/serviceAccounts/{service_account}.
service_account_idstringImmutableStable, human-readable id unique within the account, e.g. "fleet-controller". Chosen at creation and immutable.
display_namestringdescriptionstringdisabledboolDisabled service accounts fail authentication but keep their bindings, so access can be suspended without losing the grant history.
service_account_idstringRequiredgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "accounts/", "service_account": { "display_name": "My server" }, "service_account_id": "my-service-account" }' \ api.plany.cloud:443 plany.iam.v1.IamService/CreateServiceAccount{ "name": "accounts//serviceAccounts/my-service-account", "service_account_id": "my-service-account", "display_name": "My server", "description": "", "disabled": false, "create_time": "2026-10-04T12:00:00Z", "managed_by": ""}namestringRequiredgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "accounts//serviceAccounts/my-service-account" }' \ api.plany.cloud:443 plany.iam.v1.IamService/GetServiceAccount{ "name": "accounts//serviceAccounts/my-service-account", "service_account_id": "my-service-account", "display_name": "My server", "description": "", "disabled": false, "create_time": "2026-10-04T12:00:00Z", "managed_by": ""}parentstringRequiredpage_sizeint32page_tokenstringgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "accounts/", "page_size": 50 }' \ api.plany.cloud:443 plany.iam.v1.IamService/ListServiceAccounts{ "service_accounts": [ { "name": "accounts//serviceAccounts/my-service-account", "service_account_id": "my-service-account", "display_name": "My server", "description": "", "disabled": false, "create_time": "2026-10-04T12:00:00Z", "managed_by": "" } ], "next_page_token": ""}namestringRequiredgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "accounts//serviceAccounts/my-service-account" }' \ api.plany.cloud:443 plany.iam.v1.IamService/DeleteServiceAccount{}Binding grants one role to a set of members on one resource.
rolestringRequiredroles/{role}.
membersrepeated stringRequiredPrincipal names the role is granted to. Also accepts the special member
allAccountMembers, which covers every member of the owning account.
{ "role": "roles/my-role", "members": [ "" ]}One JSON Web Key.
kidstringktystringalgstringusestringcrvstringEdDSA/ECDSA curve parameters. Plany signs with EdDSA (Ed25519), so crv
is "Ed25519" and x carries the public key.
xstringystringnstringRSA parameters, populated only for legacy RS256 keys.
estring{ "kid": "", "kty": "", "alg": "", "use": "", "crv": "", "x": "", "y": "", "n": "", "e": ""}Principal is the resolved identity of a caller: who a credential belongs to, and what it may do.
namestringRequiredResource name of the underlying identity:
users/{user}
accounts/{account}/serviceAccounts/{service_account}
accounts/{account}/apiKeys/{api_key}
services/{service}
PRINCIPAL_TYPE_USERPRINCIPAL_TYPE_SERVICE_ACCOUNTPRINCIPAL_TYPE_API_KEYPRINCIPAL_TYPE_SERVICEPRINCIPAL_TYPE_OPERATORaccountstringAccount this principal acts on behalf of, accounts/{account}. Empty only
for PRINCIPAL_TYPE_SERVICE. All spend and quota consumption is attributed
here.
display_namestringDisplay name for audit logs and UIs. Never used for authorization.
emailstringVerified email, for USER principals only.
scopesrepeated stringScopes the presented credential carries, which upper-bound what the
principal may do in this call regardless of its granted roles. An API key
restricted to microvm.instances.read cannot delete an instance even if
its owner is an account admin.
project_restrictionsrepeated stringProjects the credential is restricted to, as projects/{project} names.
Empty means every project in the account, subject to role checks.
credential_idstringOpaque id of the credential used, for audit correlation and revocation.
For a JWT this is the jti claim.
expire_timeTimestampWhen the presented credential stops being valid. Calls made after this are refused.
{ "name": "", "type": "PRINCIPAL_TYPE_USER", "account": "accounts/", "display_name": "My server", "email": "", "scopes": [ "" ], "project_restrictions": [ "" ], "credential_id": "my-credential", "expire_time": "2026-10-04T12:00:00Z"}Role is a named bundle of permissions granted on an account or a project.
Plany ships predefined roles only. Custom roles are a B2B/studio ask that the model already supports — a Role resource with an explicit permission list — but exposing them is deferred until there is demand.
namestringIdentifierroles/{role}, e.g. roles/project.editor.
display_namestringOutput onlydescriptionstringOutput onlyincluded_permissionsrepeated stringOutput onlyPermissions in service.resource.verb form, e.g.
microvm.instances.create. A permission may end in * to cover a family.
SCOPE_ACCOUNTSCOPE_PROJECTSCOPE_BOTHoperator_onlyboolOutput onlyWhether this role is granted only by Plany.
You can see such a role and remove a binding of it, but SetIamPolicy refuses adding it or adding members to it. It is listed so that you can see what it is, rather than having it missing from the catalogue.
{ "name": "roles/my-role", "display_name": "My server", "description": "", "included_permissions": [ "" ], "scope": "SCOPE_ACCOUNT", "operator_only": false}ApiKey is a long-lived credential owned by an account, for scripts, CI and anything else that cannot sign in as a person.
Exchange it for a short-lived token with IssueToken. Revoking a key takes effect within that token's lifetime.
Only the key's hash is stored: the plaintext is returned exactly once, from CreateApiKey.
display_namestringRequirednamestringIdentifieraccounts/{account}/apiKeys/{api_key}.
key_prefixstringOutput onlyThe key's public prefix, e.g. "pk_live_7Fq2". Shown in UIs and logs so a key can be identified without revealing it, and used to index the lookup so verification is one hash comparison rather than a table scan.
STATE_ACTIVESTATE_DISABLEDSTATE_EXPIREDSTATE_ROTATINGSTATE_REVOKEDscopesrepeated stringPermission scopes this key may exercise, upper-bounding the roles held by the identity it acts as. Empty means every scope that identity has, which callers should avoid for keys deployed to game servers.
project_restrictionsrepeated stringProjects this key may touch. Empty means all projects in the account.
allowed_ip_rangesrepeated stringCIDR ranges the key may be presented from. Empty means anywhere. Worth setting for a key baked into a fixed fleet.
subjectstringIdentity this key authenticates as. Usually a service account; a key acting as a user inherits that user's roles and is discouraged.
expire_timeTimestampWhen the key stops working. Empty means it never expires, which the API permits but the console warns about.
Last successful use, updated at most once a minute to keep the write rate bounded. Drives "unused key" hygiene warnings.
creatorstringOutput onlyPrincipal that created the key, for audit.
Lifetime granted to JWTs minted from this key. Zero requests the platform default of one hour. Shorter lifetimes tighten the revocation window at the cost of more token exchanges.
tokensIssueTokenResponseA fresh, unrestricted token pair.
Returned because the token that made this call may not have been able to do anything else — a credential awaiting a change is issued a token scoped to this one permission — so a client with only that would have changed the password and still be unable to use the platform.
Every other token minted from the old password is revoked first.
access_tokenstringSigned JWT. Presented as authorization: Bearer <token>.
refresh_tokenstringRefresh token, issued only for interactive grants. Rotates on each use so a captured refresh token is detectable.
expire_timeTimestampscopesrepeated stringScopes actually granted, which may be narrower than requested.
principalPrincipalThe identity the token authenticates as, saved from a round trip.
namestringRequiredResource name of the underlying identity:
users/{user}
accounts/{account}/serviceAccounts/{service_account}
accounts/{account}/apiKeys/{api_key}
services/{service}
PRINCIPAL_TYPE_USERPRINCIPAL_TYPE_SERVICE_ACCOUNTPRINCIPAL_TYPE_API_KEYPRINCIPAL_TYPE_SERVICEPRINCIPAL_TYPE_OPERATORaccountstringAccount this principal acts on behalf of, accounts/{account}. Empty only
for PRINCIPAL_TYPE_SERVICE. All spend and quota consumption is attributed
here.
display_namestringDisplay name for audit logs and UIs. Never used for authorization.
emailstringVerified email, for USER principals only.
scopesrepeated stringScopes the presented credential carries, which upper-bound what the
principal may do in this call regardless of its granted roles. An API key
restricted to microvm.instances.read cannot delete an instance even if
its owner is an account admin.
project_restrictionsrepeated stringProjects the credential is restricted to, as projects/{project} names.
Empty means every project in the account, subject to role checks.
credential_idstringOpaque id of the credential used, for audit correlation and revocation.
For a JWT this is the jti claim.
expire_timeTimestampWhen the presented credential stops being valid. Calls made after this are refused.
account_warningstringSet when the credential is valid but the account needs attention before it can spend — for instance a zero credit balance. The token is still issued so the console can render the top-up flow.
password_change_requiredboolThe password must be replaced before anything else can be done.
Set for a credential IAM generated — an alpha invitation — and cleared by
ChangePassword. It is advisory to the client and not what enforces the
change: the token issued alongside it carries a single scope, so it is
refused everywhere the change-password call is not. A console that ignored
this field would render a session that could read nothing.
email_verification_requiredboolThe email address must be confirmed before anything else can be done.
Set for a user who registered and has not yet followed the link in their
verification email. Advisory, exactly as password_change_required is:
the token issued alongside it carries a single scope, so it is refused by
every service until the address is confirmed and the session refreshed.
permissionsrepeated stringThe subset of the requested permissions that are granted.
api_keyApiKeydisplay_namestringRequirednamestringIdentifieraccounts/{account}/apiKeys/{api_key}.
key_prefixstringOutput onlyThe key's public prefix, e.g. "pk_live_7Fq2". Shown in UIs and logs so a key can be identified without revealing it, and used to index the lookup so verification is one hash comparison rather than a table scan.
STATE_ACTIVESTATE_DISABLEDSTATE_EXPIREDSTATE_ROTATINGSTATE_REVOKEDscopesrepeated stringPermission scopes this key may exercise, upper-bounding the roles held by the identity it acts as. Empty means every scope that identity has, which callers should avoid for keys deployed to game servers.
project_restrictionsrepeated stringProjects this key may touch. Empty means all projects in the account.
allowed_ip_rangesrepeated stringCIDR ranges the key may be presented from. Empty means anywhere. Worth setting for a key baked into a fixed fleet.
subjectstringIdentity this key authenticates as. Usually a service account; a key acting as a user inherits that user's roles and is discouraged.
expire_timeTimestampWhen the key stops working. Empty means it never expires, which the API permits but the console warns about.
Last successful use, updated at most once a minute to keep the write rate bounded. Drives "unused key" hygiene warnings.
creatorstringOutput onlyPrincipal that created the key, for audit.
Lifetime granted to JWTs minted from this key. Zero requests the platform default of one hour. Shorter lifetimes tighten the revocation window at the cost of more token exchanges.
key_stringstringThe plaintext key, returned exactly once. Not recoverable afterwards.
kidstringktystringalgstringusestringcrvstringEdDSA/ECDSA curve parameters. Plany signs with EdDSA (Ed25519), so crv
is "Ed25519" and x carries the public key.
xstringystringnstringRSA parameters, populated only for legacy RS256 keys.
estringcache_durationDurationHow long a client may cache this set. Kept short enough that an emergency rotation propagates quickly.
access_tokenstringSigned JWT. Presented as authorization: Bearer <token>.
refresh_tokenstringRefresh token, issued only for interactive grants. Rotates on each use so a captured refresh token is detectable.
expire_timeTimestampscopesrepeated stringScopes actually granted, which may be narrower than requested.
principalPrincipalThe identity the token authenticates as, saved from a round trip.
namestringRequiredResource name of the underlying identity:
users/{user}
accounts/{account}/serviceAccounts/{service_account}
accounts/{account}/apiKeys/{api_key}
services/{service}
PRINCIPAL_TYPE_USERPRINCIPAL_TYPE_SERVICE_ACCOUNTPRINCIPAL_TYPE_API_KEYPRINCIPAL_TYPE_SERVICEPRINCIPAL_TYPE_OPERATORaccountstringAccount this principal acts on behalf of, accounts/{account}. Empty only
for PRINCIPAL_TYPE_SERVICE. All spend and quota consumption is attributed
here.
display_namestringDisplay name for audit logs and UIs. Never used for authorization.
emailstringVerified email, for USER principals only.
scopesrepeated stringScopes the presented credential carries, which upper-bound what the
principal may do in this call regardless of its granted roles. An API key
restricted to microvm.instances.read cannot delete an instance even if
its owner is an account admin.
project_restrictionsrepeated stringProjects the credential is restricted to, as projects/{project} names.
Empty means every project in the account, subject to role checks.
credential_idstringOpaque id of the credential used, for audit correlation and revocation.
For a JWT this is the jti claim.
expire_timeTimestampWhen the presented credential stops being valid. Calls made after this are refused.
account_warningstringSet when the credential is valid but the account needs attention before it can spend — for instance a zero credit balance. The token is still issued so the console can render the top-up flow.
password_change_requiredboolThe password must be replaced before anything else can be done.
Set for a credential IAM generated — an alpha invitation — and cleared by
ChangePassword. It is advisory to the client and not what enforces the
change: the token issued alongside it carries a single scope, so it is
refused everywhere the change-password call is not. A console that ignored
this field would render a session that could read nothing.
email_verification_requiredboolThe email address must be confirmed before anything else can be done.
Set for a user who registered and has not yet followed the link in their
verification email. Advisory, exactly as password_change_required is:
the token issued alongside it carries a single scope, so it is refused by
every service until the address is confirmed and the session refreshed.
api_keysrepeated ApiKeydisplay_namestringRequirednamestringIdentifieraccounts/{account}/apiKeys/{api_key}.
key_prefixstringOutput onlyThe key's public prefix, e.g. "pk_live_7Fq2". Shown in UIs and logs so a key can be identified without revealing it, and used to index the lookup so verification is one hash comparison rather than a table scan.
STATE_ACTIVESTATE_DISABLEDSTATE_EXPIREDSTATE_ROTATINGSTATE_REVOKEDscopesrepeated stringPermission scopes this key may exercise, upper-bounding the roles held by the identity it acts as. Empty means every scope that identity has, which callers should avoid for keys deployed to game servers.
project_restrictionsrepeated stringProjects this key may touch. Empty means all projects in the account.
allowed_ip_rangesrepeated stringCIDR ranges the key may be presented from. Empty means anywhere. Worth setting for a key baked into a fixed fleet.
subjectstringIdentity this key authenticates as. Usually a service account; a key acting as a user inherits that user's roles and is discouraged.
expire_timeTimestampWhen the key stops working. Empty means it never expires, which the API permits but the console warns about.
Last successful use, updated at most once a minute to keep the write rate bounded. Drives "unused key" hygiene warnings.
creatorstringOutput onlyPrincipal that created the key, for audit.
Lifetime granted to JWTs minted from this key. Zero requests the platform default of one hour. Shorter lifetimes tighten the revocation window at the cost of more token exchanges.
next_page_tokenstringrolesrepeated RolenamestringIdentifierroles/{role}, e.g. roles/project.editor.
display_namestringOutput onlydescriptionstringOutput onlyincluded_permissionsrepeated stringOutput onlyPermissions in service.resource.verb form, e.g.
microvm.instances.create. A permission may end in * to cover a family.
SCOPE_ACCOUNTSCOPE_PROJECTSCOPE_BOTHoperator_onlyboolOutput onlyWhether this role is granted only by Plany.
You can see such a role and remove a binding of it, but SetIamPolicy refuses adding it or adding members to it. It is listed so that you can see what it is, rather than having it missing from the catalogue.
next_page_tokenstringservice_accountsrepeated ServiceAccountnamestringIdentifieraccounts/{account}/serviceAccounts/{service_account}.
service_account_idstringImmutableStable, human-readable id unique within the account, e.g. "fleet-controller". Chosen at creation and immutable.
display_namestringdescriptionstringdisabledboolDisabled service accounts fail authentication but keep their bindings, so access can be suspended without losing the grant history.
managed_bystringOutput onlyThe service that created this identity for a resource of its own — e.g.
playpute.plany.cloud for a game server's, sandbox.plany.cloud for a
sandbox's. Empty for an identity a customer created.
A managed identity is created, bound and deleted by the service that owns
it, alongside the one resource it exists for. Attaching it to anything else
would tie a second resource to a lifetime nobody chose for it, so pickers
offer only identities with no managed_by. Set by IAM from the caller
that created it, never from the request.
next_page_tokenstringPolicy is the complete set of bindings on one resource.
resourcestringRequiredResource the policy attaches to, accounts/{account} or
projects/{project}.
bindingsrepeated BindingrolestringRequiredroles/{role}.
membersrepeated stringRequiredPrincipal names the role is granted to. Also accepts the special member
allAccountMembers, which covers every member of the owning account.
etagstringOpaque concurrency token. SetIamPolicy fails with ABORTED if it does not match the stored value, which makes read-modify-write safe without holding a lock.
userstringusers/{user}.
emailstringaccountstringThe account created when create_account was set, accounts/{account}.
tokensIssueTokenResponseCredentials for the new user, so registration signs them in.
access_tokenstringSigned JWT. Presented as authorization: Bearer <token>.
refresh_tokenstringRefresh token, issued only for interactive grants. Rotates on each use so a captured refresh token is detectable.
expire_timeTimestampscopesrepeated stringScopes actually granted, which may be narrower than requested.
principalPrincipalThe identity the token authenticates as, saved from a round trip.
namestringRequiredResource name of the underlying identity:
users/{user}
accounts/{account}/serviceAccounts/{service_account}
accounts/{account}/apiKeys/{api_key}
services/{service}
PRINCIPAL_TYPE_USERPRINCIPAL_TYPE_SERVICE_ACCOUNTPRINCIPAL_TYPE_API_KEYPRINCIPAL_TYPE_SERVICEPRINCIPAL_TYPE_OPERATORaccountstringAccount this principal acts on behalf of, accounts/{account}. Empty only
for PRINCIPAL_TYPE_SERVICE. All spend and quota consumption is attributed
here.
display_namestringDisplay name for audit logs and UIs. Never used for authorization.
emailstringVerified email, for USER principals only.
scopesrepeated stringScopes the presented credential carries, which upper-bound what the
principal may do in this call regardless of its granted roles. An API key
restricted to microvm.instances.read cannot delete an instance even if
its owner is an account admin.
project_restrictionsrepeated stringProjects the credential is restricted to, as projects/{project} names.
Empty means every project in the account, subject to role checks.
credential_idstringOpaque id of the credential used, for audit correlation and revocation.
For a JWT this is the jti claim.
expire_timeTimestampWhen the presented credential stops being valid. Calls made after this are refused.
account_warningstringSet when the credential is valid but the account needs attention before it can spend — for instance a zero credit balance. The token is still issued so the console can render the top-up flow.
password_change_requiredboolThe password must be replaced before anything else can be done.
Set for a credential IAM generated — an alpha invitation — and cleared by
ChangePassword. It is advisory to the client and not what enforces the
change: the token issued alongside it carries a single scope, so it is
refused everywhere the change-password call is not. A console that ignored
this field would render a session that could read nothing.
email_verification_requiredboolThe email address must be confirmed before anything else can be done.
Set for a user who registered and has not yet followed the link in their
verification email. Advisory, exactly as password_change_required is:
the token issued alongside it carries a single scope, so it is refused by
every service until the address is confirmed and the session refreshed.
tokensIssueTokenResponseA fresh session, so a reset ends signed in.
Absent — and the password still changed — when the account has a second factor enabled, because an emailed link must not stand in for it, or when the user belongs to several accounts and has to choose one at sign-in.
access_tokenstringSigned JWT. Presented as authorization: Bearer <token>.
refresh_tokenstringRefresh token, issued only for interactive grants. Rotates on each use so a captured refresh token is detectable.
expire_timeTimestampscopesrepeated stringScopes actually granted, which may be narrower than requested.
principalPrincipalThe identity the token authenticates as, saved from a round trip.
namestringRequiredResource name of the underlying identity:
users/{user}
accounts/{account}/serviceAccounts/{service_account}
accounts/{account}/apiKeys/{api_key}
services/{service}
PRINCIPAL_TYPE_USERPRINCIPAL_TYPE_SERVICE_ACCOUNTPRINCIPAL_TYPE_API_KEYPRINCIPAL_TYPE_SERVICEPRINCIPAL_TYPE_OPERATORaccountstringAccount this principal acts on behalf of, accounts/{account}. Empty only
for PRINCIPAL_TYPE_SERVICE. All spend and quota consumption is attributed
here.
display_namestringDisplay name for audit logs and UIs. Never used for authorization.
emailstringVerified email, for USER principals only.
scopesrepeated stringScopes the presented credential carries, which upper-bound what the
principal may do in this call regardless of its granted roles. An API key
restricted to microvm.instances.read cannot delete an instance even if
its owner is an account admin.
project_restrictionsrepeated stringProjects the credential is restricted to, as projects/{project} names.
Empty means every project in the account, subject to role checks.
credential_idstringOpaque id of the credential used, for audit correlation and revocation.
For a JWT this is the jti claim.
expire_timeTimestampWhen the presented credential stops being valid. Calls made after this are refused.
account_warningstringSet when the credential is valid but the account needs attention before it can spend — for instance a zero credit balance. The token is still issued so the console can render the top-up flow.
password_change_requiredboolThe password must be replaced before anything else can be done.
Set for a credential IAM generated — an alpha invitation — and cleared by
ChangePassword. It is advisory to the client and not what enforces the
change: the token issued alongside it carries a single scope, so it is
refused everywhere the change-password call is not. A console that ignored
this field would render a session that could read nothing.
email_verification_requiredboolThe email address must be confirmed before anything else can be done.
Set for a user who registered and has not yet followed the link in their
verification email. Advisory, exactly as password_change_required is:
the token issued alongside it carries a single scope, so it is refused by
every service until the address is confirmed and the session refreshed.
emailstringThe address the reset was for, so a client can prefill sign-in.
ServiceAccount is a non-human identity owned by an account.
It is what a studio's CI, matchmaker or fleet controller authenticates as. Roles are granted to a service account exactly as they are to a user, so the authorization path has no special cases.
namestringIdentifieraccounts/{account}/serviceAccounts/{service_account}.
service_account_idstringImmutableStable, human-readable id unique within the account, e.g. "fleet-controller". Chosen at creation and immutable.
display_namestringdescriptionstringdisabledboolDisabled service accounts fail authentication but keep their bindings, so access can be suspended without losing the grant history.
managed_bystringOutput onlyThe service that created this identity for a resource of its own — e.g.
playpute.plany.cloud for a game server's, sandbox.plany.cloud for a
sandbox's. Empty for an identity a customer created.
A managed identity is created, bound and deleted by the service that owns
it, alongside the one resource it exists for. Attaching it to anything else
would tie a second resource to a lifetime nobody chose for it, so pickers
offer only identities with no managed_by. Set by IAM from the caller
that created it, never from the request.
emailstringThe address that is now confirmed.
| Value | Description |
|---|---|
| STATE_UNSPECIFIED | |
| STATE_ACTIVE | Usable. |
| STATE_DISABLED | Manually disabled. Rejected, but retained so it can be re-enabled. |
| STATE_EXPIRED | Past expire_time. |
| STATE_ROTATING | Superseded by a rotation and inside its grace period. Still accepted, so a rotation does not require an atomic deploy. |
| STATE_REVOKED | Permanently revoked. Never accepted again. |
GrantType names the credential being exchanged in IssueTokenRequest.
| Value | Description |
|---|---|
| GRANT_TYPE_UNSPECIFIED | |
| GRANT_TYPE_PASSWORD | Email and password, for the console's interactive sign-in. |
| GRANT_TYPE_API_KEY | A raw API key, exchanged for a scoped JWT. |
| GRANT_TYPE_REFRESH_TOKEN | A refresh token from an earlier issuance. |
| GRANT_TYPE_SERVICE_ACCOUNT_ASSERTION | A JWT the service account signed with its own key, proving possession without ever transmitting a shared secret. |
| GRANT_TYPE_OIDC_ASSERTION | An OIDC id token from a federated provider (GitHub Actions, Google). Lets a studio's CI authenticate with no stored secret at all. |
PrincipalType is the kind of identity behind a call.
| Value | Description |
|---|---|
| PRINCIPAL_TYPE_UNSPECIFIED | |
| PRINCIPAL_TYPE_USER | A human, authenticated interactively and holding a short-lived JWT. |
| PRINCIPAL_TYPE_SERVICE_ACCOUNT | A non-human identity owned by an account, used by customer automation. Holds a JWT obtained by signing an assertion with its own key. |
| PRINCIPAL_TYPE_API_KEY | A long-lived credential owned by an account, exchanged for a short-lived token with `IssueToken`. |
| PRINCIPAL_TYPE_SERVICE | An internal Plany service calling another. Authenticated by workload identity (mTLS in-cluster), not by a customer credential. |
| PRINCIPAL_TYPE_OPERATOR | A person at Plany acting on an account, for example while answering a support case. Always audited, and never able to spend an account's credit. |
Scope at which a role can be bound.
| Value | Description |
|---|---|
| SCOPE_UNSPECIFIED | |
| SCOPE_ACCOUNT | Bindable on an account only. |
| SCOPE_PROJECT | Bindable on a project only. |
| SCOPE_BOTH | Bindable at either level; a binding on the account is inherited by all of its projects. |