Store credentials — database passwords, API keys, licence files — and reference them by name from everything that needs them. A secret holds versions: change its value by adding a version, and every reference to the secret picks up the latest enabled one. Reading a secret's value needs its own permission, separate from seeing that it exists, so a game server can be allowed to read one secret and nothing else. Secrets are regional; a project using the same credential in two regions holds a secret in each.
Creates a secret, optionally with its first version.
initial_payload is a deliberate departure from creating the container and
the value in two calls. A secret with no version exists, holds its
bindings, appears in every list, and fails every access — so the two-call
shape has a window in which the customer's configuration is complete and
broken, and a Terraform apply interrupted between the two leaves exactly
that. One call has no such window.
parentstringRequiredprojects/{project}/locations/{location}.
namestringIdentifierprojects/{project}/locations/{location}/secrets/{secret}.
Regional: a secret lives in one region, and a project using the same credential in two regions holds a secret in each.
display_namestringWhat a person calls it. Free text.
descriptionstringWhat it is for, in the customer's own words. Worth having on a resource whose value nobody can look at to remind themselves.
labelsmap<string, string>secret_idstringRequiredThe last segment of the name. AIP-122: lower case, digits and hyphens.
The first version's value.
Optional but almost always wanted — see the method comment. Left out, the
secret is created with no versions and latest_version empty.
databytesBytes, not a string, because a secret is as often a private key or a keytab as it is a password.
Environment injection needs valid UTF-8 and says so at the point of use —
see plany.playpute.v1.SecretAttachment. Storing it as text here instead
would make the platform unable to hold a certificate, which is the second
thing anybody asks for.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "projects//locations/", "secret": { "display_name": "My server" }, "secret_id": "my-secret" }' \ api.plany.cloud:443 plany.secret.v1.SecretService/CreateSecret{ "name": "projects//locations//secrets/", "uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11", "display_name": "My server", "description": "", "labels": { "key": "" }, "state": "STATE_ACTIVE", "version_count": "0", "latest_version": "projects//locations//secrets//versions/my-version", "payload_fingerprint": "", "managed_by": "", "unmetered": false, "last_access_time": "2026-10-04T12:00:00Z", "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\""}grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "projects//locations//secrets/" }' \ api.plany.cloud:443 plany.secret.v1.SecretService/GetSecret{ "name": "projects//locations//secrets/", "uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11", "display_name": "My server", "description": "", "labels": { "key": "" }, "state": "STATE_ACTIVE", "version_count": "0", "latest_version": "projects//locations//secrets//versions/my-version", "payload_fingerprint": "", "managed_by": "", "unmetered": false, "last_access_time": "2026-10-04T12:00:00Z", "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\""}parentstringRequiredpage_sizeint32page_tokenstringfilterstringAIP-160, over display_name, labels, state and managed_by.
customer_onlyboolLeave out the secrets Plany stored on your behalf for another product. Off by default, so the list shows every credential held in the project.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "projects//locations/", "page_size": 50 }' \ api.plany.cloud:443 plany.secret.v1.SecretService/ListSecrets{ "secrets": [ { "name": "projects//locations//secrets/", "uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11", "display_name": "My server", "description": "", "labels": { "key": "" }, "state": "STATE_ACTIVE", "version_count": "0", "latest_version": "projects//locations//secrets//versions/my-version", "payload_fingerprint": "", "managed_by": "", "unmetered": false, "last_access_time": "2026-10-04T12:00:00Z", "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\"" } ], "next_page_token": ""}Updates a secret's metadata. Never its value — see AddSecretVersion.
namestringIdentifierprojects/{project}/locations/{location}/secrets/{secret}.
Regional: a secret lives in one region, and a project using the same credential in two regions holds a secret in each.
display_namestringWhat a person calls it. Free text.
descriptionstringWhat it is for, in the customer's own words. Worth having on a resource whose value nobody can look at to remind themselves.
labelsmap<string, string>Which fields to write. Only display_name, description and labels can
be updated; the value is not among them and never will be.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "secret": { "display_name": "My server" }, "update_mask": "display_name" }' \ api.plany.cloud:443 plany.secret.v1.SecretService/UpdateSecret{ "name": "projects//locations//secrets/", "uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11", "display_name": "My server", "description": "", "labels": { "key": "" }, "state": "STATE_ACTIVE", "version_count": "0", "latest_version": "projects//locations//secrets//versions/my-version", "payload_fingerprint": "", "managed_by": "", "unmetered": false, "last_access_time": "2026-10-04T12:00:00Z", "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\""}Deletes a secret and destroys every version it holds.
Refused for a secret Plany stored on your behalf for another product (managed_by is set): delete the resource that created it instead.
Refused, too, while something still references it, unless force is set — a delete that took the credential out from under a running fleet is an outage.
namestringRequiredforceboolDelete it even though something still references it.
etagstringRefuse if the secret has changed since it was read.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "projects//locations//secrets/" }' \ api.plany.cloud:443 plany.secret.v1.SecretService/DeleteSecret{}Adds a new version, which immediately becomes the latest.
parentstringRequiredprojects/{project}/locations/{location}/secrets/{secret}.
databytesBytes, not a string, because a secret is as often a private key or a keytab as it is a password.
Environment injection needs valid UTF-8 and says so at the point of use —
see plany.playpute.v1.SecretAttachment. Storing it as text here instead
would make the platform unable to hold a certificate, which is the second
thing anybody asks for.
disable_previousboolDisable the version this one replaces, in the same call.
The safe rotation in one step, and off by default because the unsafe order is the one people reach for: disabling first leaves a window where nothing works, and doing it afterwards by hand leaves two live credentials if the second call never happens.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "projects//locations//secrets/", "payload": {} }' \ api.plany.cloud:443 plany.secret.v1.SecretService/AddSecretVersion{ "name": "projects//locations//secrets//versions/my-version", "state": "STATE_ENABLED", "payload_size_bytes": 0, "payload_fingerprint": "", "created_by": "", "create_time": "2026-10-04T12:00:00Z", "state_change_time": "2026-10-04T12:00:00Z", "last_access_time": "2026-10-04T12:00:00Z"}namestringRequiredThe version's full name. The last segment may be latest, which resolves
to the newest enabled version; the answer always names the number.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "projects//locations//secrets//versions/my-version" }' \ api.plany.cloud:443 plany.secret.v1.SecretService/GetSecretVersion{ "name": "projects//locations//secrets//versions/my-version", "state": "STATE_ENABLED", "payload_size_bytes": 0, "payload_fingerprint": "", "created_by": "", "create_time": "2026-10-04T12:00:00Z", "state_change_time": "2026-10-04T12:00:00Z", "last_access_time": "2026-10-04T12:00:00Z"}parentstringRequiredpage_sizeint32page_tokenstringgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "projects//locations//secrets/", "page_size": 50 }' \ api.plany.cloud:443 plany.secret.v1.SecretService/ListSecretVersions{ "versions": [ { "name": "projects//locations//secrets//versions/my-version", "state": "STATE_ENABLED", "payload_size_bytes": 0, "payload_fingerprint": "", "created_by": "", "create_time": "2026-10-04T12:00:00Z", "state_change_time": "2026-10-04T12:00:00Z", "last_access_time": "2026-10-04T12:00:00Z" } ], "next_page_token": ""}Returns the value.
The only call that discloses a secret's value, and it needs its own permission, secretmanager.versions.access. Every call is recorded in your audit trail, without the value.
namestringRequiredThe version's full name, or one ending in latest.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "projects//locations//secrets//versions/my-version" }' \ api.plany.cloud:443 plany.secret.v1.SecretService/AccessSecretVersion{ "name": "", "payload": { "data": "" }}Stops a version being served, keeping its bytes.
namestringRequiredgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "projects//locations//secrets//versions/my-version" }' \ api.plany.cloud:443 plany.secret.v1.SecretService/DisableSecretVersion{ "name": "projects//locations//secrets//versions/my-version", "state": "STATE_ENABLED", "payload_size_bytes": 0, "payload_fingerprint": "", "created_by": "", "create_time": "2026-10-04T12:00:00Z", "state_change_time": "2026-10-04T12:00:00Z", "last_access_time": "2026-10-04T12:00:00Z"}namestringRequiredgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "projects//locations//secrets//versions/my-version" }' \ api.plany.cloud:443 plany.secret.v1.SecretService/EnableSecretVersion{ "name": "projects//locations//secrets//versions/my-version", "state": "STATE_ENABLED", "payload_size_bytes": 0, "payload_fingerprint": "", "created_by": "", "create_time": "2026-10-04T12:00:00Z", "state_change_time": "2026-10-04T12:00:00Z", "last_access_time": "2026-10-04T12:00:00Z"}Destroys a version's bytes. Irreversible.
The row survives so the version number is never reissued and the history still reads as one. Refused on the latest enabled version of a secret something still references, for the reason DeleteSecret is.
namestringRequiredgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "projects//locations//secrets//versions/my-version" }' \ api.plany.cloud:443 plany.secret.v1.SecretService/DestroySecretVersion{ "name": "projects//locations//secrets//versions/my-version", "state": "STATE_ENABLED", "payload_size_bytes": 0, "payload_fingerprint": "", "created_by": "", "create_time": "2026-10-04T12:00:00Z", "state_change_time": "2026-10-04T12:00:00Z", "last_access_time": "2026-10-04T12:00:00Z"}SecretPayload is the value itself.
The only message on this API that carries one, returned by the only RPC that
returns one. Separated from SecretVersion so that nothing can accidentally
serve a payload by returning a version: a handler that wants to disclose the
bytes has to name this type, which makes the disclosure a thing somebody
wrote rather than a field somebody forgot to strip.
databytesBytes, not a string, because a secret is as often a private key or a keytab as it is a password.
Environment injection needs valid UTF-8 and says so at the point of use —
see plany.playpute.v1.SecretAttachment. Storing it as text here instead
would make the platform unable to hold a certificate, which is the second
thing anybody asks for.
{ "data": ""}namestringThe version actually served, by number, never latest.
So a caller that asked for latest can record what it got. A workload that
logged "read the secret" and not "read version 4" cannot answer the only
question worth asking after a rotation goes wrong.
payloadSecretPayloaddatabytesBytes, not a string, because a secret is as often a private key or a keytab as it is a password.
Environment injection needs valid UTF-8 and says so at the point of use —
see plany.playpute.v1.SecretAttachment. Storing it as text here instead
would make the platform unable to hold a certificate, which is the second
thing anybody asks for.
versionsrepeated SecretVersionNewest first, which is the order somebody asking about a rotation wants.
namestringIdentifier.../secrets/{secret}/versions/{version}, where the last segment is a
positive integer that increases and is never reused.
Deliberately not latest as a stored name. latest is an alias accepted
on the read path and resolved to a number, so an audit entry and a log line
always name the version that was actually served.
STATE_ENABLEDSTATE_DISABLEDSTATE_DESTROYEDpayload_size_bytesint32Output onlySize of the payload. Metadata, so it comes back from an ordinary read — knowing a key is 32 bytes and not 3 is how a paste that lost its tail is found without anybody looking at either.
payload_fingerprintstringOutput onlyA truncated SHA-256 over the payload. See Secret.payload_fingerprint.
created_bystringOutput onlyWho added it, as a principal name. Empty when the platform did.
When this version was disabled or destroyed, whichever happened.
next_page_tokenstringsecretsrepeated SecretnamestringIdentifierprojects/{project}/locations/{location}/secrets/{secret}.
Regional: a secret lives in one region, and a project using the same credential in two regions holds a secret in each.
uidstringOutput onlyServer-assigned, immutable, unique for the life of the platform.
display_namestringWhat a person calls it. Free text.
descriptionstringWhat it is for, in the customer's own words. Worth having on a resource whose value nobody can look at to remind themselves.
labelsmap<string, string>STATE_ACTIVESTATE_DELETINGversion_countint64Output onlyThe highest version number ever added, whatever its state.
A counter rather than a count: it never goes down, so a destroyed version's
number is never reissued and an audit entry naming versions/3 means one
thing forever.
latest_versionstringOutput only.../secrets/{secret}/versions/{version} — the newest ENABLED version,
which is what an unqualified reference resolves to.
Empty for a secret with no usable version, which is a real state and worth rendering: a secret whose only version was destroyed still exists, still holds its bindings, and fails every access until somebody adds one.
payload_fingerprintstringOutput onlyA truncated hash of the latest enabled version's payload.
The whole of what a read tells you about the value. Two secrets holding one credential share it, so "did I paste the same key into both" is answerable without either being disclosed — and so is "did my rotation take", which is the question a customer actually has after a write they cannot verify.
managed_bystringOutput onlyThe Plany product that stored this secret on your behalf, if any. Empty for a secret you created.
A managed secret is not charged for, and cannot be deleted directly — it goes when the resource that created it does.
unmeteredboolOutput onlyNot charged for. True exactly when managed_by is set.
When a version of this secret was last handed to somebody.
The one field here worth more than the rest put together. It is how a customer finds the API key nothing has used since March — which is the credential worth revoking — and it is the first thing to look at when asking whether a leaked key was ever actually used.
etagstringOutput onlynext_page_tokenstringSecret is a named credential a project holds.
It carries no sensitive bytes. What it carries is everything about the credential — its name, who may reach it, how many versions it has had, when it was last read — which is the half that belongs in a list, a console page and an audit entry.
namestringIdentifierprojects/{project}/locations/{location}/secrets/{secret}.
Regional: a secret lives in one region, and a project using the same credential in two regions holds a secret in each.
uidstringOutput onlyServer-assigned, immutable, unique for the life of the platform.
display_namestringWhat a person calls it. Free text.
descriptionstringWhat it is for, in the customer's own words. Worth having on a resource whose value nobody can look at to remind themselves.
labelsmap<string, string>STATE_ACTIVESTATE_DELETINGversion_countint64Output onlyThe highest version number ever added, whatever its state.
A counter rather than a count: it never goes down, so a destroyed version's
number is never reissued and an audit entry naming versions/3 means one
thing forever.
latest_versionstringOutput only.../secrets/{secret}/versions/{version} — the newest ENABLED version,
which is what an unqualified reference resolves to.
Empty for a secret with no usable version, which is a real state and worth rendering: a secret whose only version was destroyed still exists, still holds its bindings, and fails every access until somebody adds one.
payload_fingerprintstringOutput onlyA truncated hash of the latest enabled version's payload.
The whole of what a read tells you about the value. Two secrets holding one credential share it, so "did I paste the same key into both" is answerable without either being disclosed — and so is "did my rotation take", which is the question a customer actually has after a write they cannot verify.
managed_bystringOutput onlyThe Plany product that stored this secret on your behalf, if any. Empty for a secret you created.
A managed secret is not charged for, and cannot be deleted directly — it goes when the resource that created it does.
unmeteredboolOutput onlyNot charged for. True exactly when managed_by is set.
When a version of this secret was last handed to somebody.
The one field here worth more than the rest put together. It is how a customer finds the API key nothing has used since March — which is the credential worth revoking — and it is the first thing to look at when asking whether a leaked key was ever actually used.
etagstringOutput onlySecretVersion is one immutable value a secret has held.
Immutable on purpose: a credential that could be edited in place has no history, so "when did this change and to what" is unanswerable, and anything caching it has no way to know it went stale. Changing a secret is adding a version.
namestringIdentifier.../secrets/{secret}/versions/{version}, where the last segment is a
positive integer that increases and is never reused.
Deliberately not latest as a stored name. latest is an alias accepted
on the read path and resolved to a number, so an audit entry and a log line
always name the version that was actually served.
STATE_ENABLEDSTATE_DISABLEDSTATE_DESTROYEDpayload_size_bytesint32Output onlySize of the payload. Metadata, so it comes back from an ordinary read — knowing a key is 32 bytes and not 3 is how a paste that lost its tail is found without anybody looking at either.
payload_fingerprintstringOutput onlyA truncated SHA-256 over the payload. See Secret.payload_fingerprint.
created_bystringOutput onlyWho added it, as a principal name. Empty when the platform did.
When this version was disabled or destroyed, whichever happened.
| Value | Description |
|---|---|
| STATE_UNSPECIFIED | |
| STATE_ACTIVE | Usable. Versions can be added and accessed. |
| STATE_DELETING | The row is gone from every list and its versions are being shredded. A delete is not instant, and a secret in this state still refuses every access rather than serving one last read. |
| Value | Description |
|---|---|
| STATE_UNSPECIFIED | |
| STATE_ENABLED | Accessible. |
| STATE_DISABLED | Retained but refused. The step a careful rotation takes before destroying: disable, watch for the thing that still needed it to break, and re-enable if it does. Destroying instead makes that recovery impossible. |
| STATE_DESTROYED | The ciphertext is gone. Irreversible. The row stays, so the version number is never reissued and the history still reads as a history — what is destroyed is the value, not the fact that there was one. |