Read the record of who did what on your account. Every call that changes something — on any product, in any region — is recorded once, with who made it, from where, and whether it was allowed. The trail is append-only and has no write or delete method, and VerifyIntegrity proves that no entry has been altered or removed.
Lists audit events for an account, newest first.
parentstringRequiredaccounts/{account}
page_sizeint32page_tokenstringOpaque. Keyset over (event_time, sequence) rather than an offset: an audit table is append-heavy and deep, and an offset scan degrades linearly with page depth exactly when somebody is paging back through an incident.
filterAuditFilterprojectstringRestrict to one project.
resourcestringRestrict to one resource, exactly. The "everything that happened to this game server" query.
principalstringRestrict to one actor.
credential_idstringRestrict to one credential. What a leaked API key is investigated with.
servicestringRestrict to one service, e.g. playpute.
methodstringRestrict to one full method.
locationstringRestrict to one region.
time_rangeIntervalWindow, by event time. Half-open: start inclusive, end exclusive.
failures_onlyboolOnly calls that failed.
The security view. A run of denials from one credential is the clearest available signal that somebody is trying doors.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "accounts/", "page_size": 50 }' \ api.plany.cloud:443 plany.audit.v1.AuditService/ListAuditEvents{ "audit_events": [ { "name": "accounts//auditEvents/my-audit-event", "method": "", "service": "", "resource": "", "account": "accounts/", "project": "projects/", "location": "", "actor": { "principal": "", "principal_type": "", "email": "", "ip_address": "", "user_agent": "", "credential_id": "my-credential" }, "status": {}, "request_json": "", "operation": "", "trace_id": "my-trace", "event_time": "2026-10-04T12:00:00Z", "duration_micros": "0", "sequence": "0", "hash": "", "previous_hash": "", "mutating": false } ], "next_page_token": ""}namestringRequiredgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "accounts//auditEvents/my-audit-event" }' \ api.plany.cloud:443 plany.audit.v1.AuditService/GetAuditEvent{ "name": "accounts//auditEvents/my-audit-event", "method": "", "service": "", "resource": "", "account": "accounts/", "project": "projects/", "location": "", "actor": { "principal": "", "principal_type": "", "email": "", "ip_address": "", "user_agent": "", "credential_id": "my-credential" }, "status": {}, "request_json": "", "operation": "", "trace_id": "my-trace", "event_time": "2026-10-04T12:00:00Z", "duration_micros": "0", "sequence": "0", "hash": "", "previous_hash": "", "mutating": false}Verifies that an account's chain has not been tampered with.
The RPC that makes the trail worth having in a dispute. It recomputes every hash over a window and checks each links to the one before, so "nothing was altered or removed" is something the platform can demonstrate rather than assert.
parentstringRequiredtime_rangeIntervalWindow to verify. Defaults to the whole chain, which is the honest default — verifying a window somebody chose proves less than verifying all of it.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "" }' \ api.plany.cloud:443 plany.audit.v1.AuditService/VerifyIntegrity{ "intact": false, "events_checked": "0", "breaks": [ { "audit_event": "", "sequence": "0", "kind": "KIND_CONTENT_ALTERED", "detail": "" } ], "verified_range": { "start_time": "2026-10-01T00:00:00Z", "end_time": "2026-10-04T00:00:00Z" }}Summarises an account's activity over a period: counts by kind of change, as shown on the console's overview.
Counted over every entry in the period, not only the first page, so the totals are exact.
parentstringRequiredtime_rangeIntervalgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "" }' \ api.plany.cloud:443 plany.audit.v1.AuditService/SummariseActivity{ "total_events": "0", "failed_events": "0", "by_principal": [ { "key": "", "events": "0" } ], "by_service": [ { "key": "", "events": "0" } ], "by_method": [ { "key": "", "events": "0" } ], "daily": [ { "start_time": "2026-10-04T12:00:00Z", "events": "0", "failed": "0" } ], "time_range": { "start_time": "2026-10-01T00:00:00Z", "end_time": "2026-10-04T00:00:00Z" }}Who made the call.
principalstringPrincipal name, e.g. users/u_123 or serviceAccounts/sa_456.
principal_typestringPrincipalType as a string: USER, SERVICE_ACCOUNT, API_KEY, SERVICE.
emailstringEmail at the time of the call, denormalised on purpose.
A user can be renamed or removed, and an audit trail that resolved identities at read time would quietly rewrite history — the entry would stop naming the person who actually did it.
ip_addressstringWhere the call came from, as Plany's API saw it.
user_agentstringcredential_idstringWhich credential was used.
The field that turns "an API key was leaked" from a panic into a query: everything that key ever did, in one filter.
{ "principal": "", "principal_type": "", "email": "", "ip_address": "", "user_agent": "", "credential_id": "my-credential"}AuditFilter narrows a listing.
Structured fields rather than a query language. Every one of these is an indexed column, so a filter cannot express a question the store would have to answer with a full scan — which is what an expression language would allow, and what would make one customer's clever query everybody's outage.
projectstringRestrict to one project.
resourcestringRestrict to one resource, exactly. The "everything that happened to this game server" query.
principalstringRestrict to one actor.
credential_idstringRestrict to one credential. What a leaked API key is investigated with.
servicestringRestrict to one service, e.g. playpute.
methodstringRestrict to one full method.
locationstringRestrict to one region.
time_rangeIntervalWindow, by event time. Half-open: start inclusive, end exclusive.
failures_onlyboolOnly calls that failed.
The security view. A run of denials from one credential is the clearest available signal that somebody is trying doors.
{ "project": "projects/", "resource": "", "principal": "", "credential_id": "my-credential", "service": "", "method": "", "location": "", "time_range": { "start_time": "2026-10-01T00:00:00Z", "end_time": "2026-10-04T00:00:00Z" }, "failures_only": false}Events per day across the window, oldest first. What the console draws.
start_timeTimestampeventsint64failedint64{ "start_time": "2026-10-04T12:00:00Z", "events": "0", "failed": "0"}One row of a breakdown.
keystringeventsint64{ "key": "", "events": "0"}Where the chain first broke, if it did.
audit_eventstringEntry whose hash or link did not check out.
sequenceint64KIND_CONTENT_ALTEREDKIND_CHAIN_BROKENKIND_SEQUENCE_GAPdetailstring{ "audit_event": "", "sequence": "0", "kind": "KIND_CONTENT_ALTERED", "detail": ""}AuditEvent is one thing somebody did to an account.
Every call that changes state on the platform produces exactly one, whichever product it was made to. It is not a log line: it answers "who deleted the production world save, from where, and were they allowed to" months later, which is why the trail is append-only and chained so that altering any entry is detectable.
namestringIdentifieraccounts/{account}/auditEvents/{audit_event}
methodstringOutput onlyFull gRPC method, e.g. /plany.playpute.v1.PlayputeService/DeleteGameServer.
servicestringOutput onlyService that handled it, e.g. playpute.
resourcestringOutput onlyResource acted on, as a resource name.
Extracted from the request's name or parent field by the interceptor,
so it is populated without any handler cooperating. This is what makes
"everything that ever happened to this game server" a filter rather than a
text search.
accountstringOutput onlyprojectstringOutput onlylocationstringOutput onlyRegion the call was served in. Empty for the global plane.
principalstringPrincipal name, e.g. users/u_123 or serviceAccounts/sa_456.
principal_typestringPrincipalType as a string: USER, SERVICE_ACCOUNT, API_KEY, SERVICE.
emailstringEmail at the time of the call, denormalised on purpose.
A user can be renamed or removed, and an audit trail that resolved identities at read time would quietly rewrite history — the entry would stop naming the person who actually did it.
ip_addressstringWhere the call came from, as Plany's API saw it.
user_agentstringcredential_idstringWhich credential was used.
The field that turns "an API key was leaked" from a panic into a query: everything that key ever did, in one filter.
Outcome. OK means it succeeded.
Failures are recorded too, and that is not padding: a run of PERMISSION_DENIED from one credential is the clearest signal available that somebody is trying doors, and a trail that only kept successes would show nothing at all until one opened.
request_jsonstringOutput onlyThe request, as JSON, with secrets removed.
Redaction is by field name at the interceptor — password, secret, token, key, credential — and the payload is capped. Storing a request verbatim would put customer passwords and API keys into the one table designed never to be deleted.
operationstringOutput onlyLong-running operation the call started, if any. Lets a slow create be followed from the audit entry to its outcome.
trace_idstringOutput onlyTrace id, so an entry links to the full distributed trace.
duration_microsint64Output onlyHow long the call took.
sequenceint64Output onlyPosition in this account's chain, starting at 1.
Monotonic and gapless. A missing number is the only evidence a deletion leaves, so it is the first thing integrity verification looks at.
hashstringOutput onlySHA-256 over this entry's content and the previous entry's hash.
The chain is what makes the trail tamper-evident rather than merely append-only. Editing one entry changes its hash, which breaks every entry after it — so altering history means rewriting all of it, and removing an entry leaves a gap that cannot be closed.
previous_hashstringOutput onlyThe previous entry's hash. Empty for the first entry of an account.
mutatingboolOutput onlyWhether the call changed state. Only calls that change state are recorded, so this is always true.
audit_eventsrepeated AuditEventnamestringIdentifieraccounts/{account}/auditEvents/{audit_event}
methodstringOutput onlyFull gRPC method, e.g. /plany.playpute.v1.PlayputeService/DeleteGameServer.
servicestringOutput onlyService that handled it, e.g. playpute.
resourcestringOutput onlyResource acted on, as a resource name.
Extracted from the request's name or parent field by the interceptor,
so it is populated without any handler cooperating. This is what makes
"everything that ever happened to this game server" a filter rather than a
text search.
accountstringOutput onlyprojectstringOutput onlylocationstringOutput onlyRegion the call was served in. Empty for the global plane.
principalstringPrincipal name, e.g. users/u_123 or serviceAccounts/sa_456.
principal_typestringPrincipalType as a string: USER, SERVICE_ACCOUNT, API_KEY, SERVICE.
emailstringEmail at the time of the call, denormalised on purpose.
A user can be renamed or removed, and an audit trail that resolved identities at read time would quietly rewrite history — the entry would stop naming the person who actually did it.
ip_addressstringWhere the call came from, as Plany's API saw it.
user_agentstringcredential_idstringWhich credential was used.
The field that turns "an API key was leaked" from a panic into a query: everything that key ever did, in one filter.
Outcome. OK means it succeeded.
Failures are recorded too, and that is not padding: a run of PERMISSION_DENIED from one credential is the clearest signal available that somebody is trying doors, and a trail that only kept successes would show nothing at all until one opened.
request_jsonstringOutput onlyThe request, as JSON, with secrets removed.
Redaction is by field name at the interceptor — password, secret, token, key, credential — and the payload is capped. Storing a request verbatim would put customer passwords and API keys into the one table designed never to be deleted.
operationstringOutput onlyLong-running operation the call started, if any. Lets a slow create be followed from the audit entry to its outcome.
trace_idstringOutput onlyTrace id, so an entry links to the full distributed trace.
duration_microsint64Output onlyHow long the call took.
sequenceint64Output onlyPosition in this account's chain, starting at 1.
Monotonic and gapless. A missing number is the only evidence a deletion leaves, so it is the first thing integrity verification looks at.
hashstringOutput onlySHA-256 over this entry's content and the previous entry's hash.
The chain is what makes the trail tamper-evident rather than merely append-only. Editing one entry changes its hash, which breaks every entry after it — so altering history means rewriting all of it, and removing an entry leaves a gap that cannot be closed.
previous_hashstringOutput onlyThe previous entry's hash. Empty for the first entry of an account.
mutatingboolOutput onlyWhether the call changed state. Only calls that change state are recorded, so this is always true.
next_page_tokenstringtotal_eventsint64failed_eventsint64by_principalrepeated SummariseActivityResponse.CountBusiest actors, services and methods, largest first.
keystringeventsint64by_servicerepeated SummariseActivityResponse.Countkeystringeventsint64keystringeventsint64start_timeTimestampeventsint64failedint64time_rangeIntervalintactboolWhether every entry checked hashes correctly and links to its predecessor.
events_checkedint64audit_eventstringEntry whose hash or link did not check out.
sequenceint64KIND_CONTENT_ALTEREDKIND_CHAIN_BROKENKIND_SEQUENCE_GAPdetailstringverified_rangeIntervalThe window actually verified.
| Value | Description |
|---|---|
| KIND_UNSPECIFIED | |
| KIND_CONTENT_ALTERED | The entry's content no longer hashes to its recorded hash: it was edited in place. |
| KIND_CHAIN_BROKEN | The entry does not link to its predecessor's hash: something between them was removed or replaced. |
| KIND_SEQUENCE_GAP | A sequence number is missing: an entry was deleted outright. |