Ask Plany for help, and keep the whole conversation in one place. Open a case about a product, a feature and optionally the exact resource that is misbehaving, attach screenshots or logs, and reply on it until it is resolved. The people answering see the case alongside the resource it names, so you do not have to explain which server you mean.
The products and features a case can be about.
No parameters.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ api.plany.cloud:443 plany.support.v1.SupportService/ListSupportTopics{ "topics": [ { "id": "", "display_name": "My server", "description": "", "features": [ { "id": "", "display_name": "My server" } ], "resource_types": [ "" ] } ]}Issues a URL to upload one file to, before it is attached to anything.
parentstringRequiredaccounts/{account}.
filenamestringRequiredThe file's own name, as the customer's machine called it. Shown, never used as a path.
content_typestringRequiredImages, video, plain text, logs, JSON, PDF or zip. Anything else is refused when the upload is requested.
size_bytesint64RequiredWhat the client said it would upload; once attached, what actually arrived.
namestringIdentifieraccounts/{account}/attachments/{attachment}.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "accounts/", "attachment": { "filename": "", "content_type": "", "size_bytes": "0" } }' \ api.plany.cloud:443 plany.support.v1.SupportService/CreateAttachment{ "attachment": { "name": "accounts//attachments/my-attachment", "filename": "", "content_type": "", "size_bytes": "0", "state": "STATE_PENDING", "create_time": "2026-10-04T12:00:00Z" }, "upload_url": "", "upload_headers": { "key": "" }, "expire_time": "2026-10-04T12:00:00Z"}A short-lived URL to read an attached file back.
Authorized as reading the case the file is attached to. A pending upload nobody attached has no case, and is readable by nobody.
namestringRequiredgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "accounts//attachments/my-attachment" }' \ api.plany.cloud:443 plany.support.v1.SupportService/GetAttachmentDownloadUrl{ "url": "", "expire_time": "2026-10-04T12:00:00Z"}Opens a case.
The case starts with two messages: your description with its attachments, and an acknowledgement naming who will answer. Both exist before this returns.
parentstringRequiredaccounts/{account}.
productstringRequiredImmutableThe product the case is about, as a SupportTopic.id — playpute,
sandbox, billing, other.
featurestringRequiredImmutableThe part of the product, as a SupportFeature.id within it. other is
always valid.
titlestringRequiredOne line, written by the customer. At most 140 characters.
descriptionstringRequiredImmutableWhat the customer wrote when they opened the case. Also the first message on the timeline, with the attachments that came with it; kept here so a list can show it without reading the timeline.
namestringIdentifieraccounts/{account}/cases/{case}.
resourcestringImmutableThe one resource the case is about, if there is one — projects/p/locations/eu-west-2/gameServers/survival.
Optional, and the most useful field on the case when set: it lets the person answering see that server's state and recent logs straight away rather than asking which one you mean. Must belong to the case's account.
attachmentsrepeated stringUploaded attachments to put on the opening message, by name. Each must be this account's, pending, and actually uploaded.
request_idstringMakes a retried create return the case the first attempt made rather than opening a second one. A double-click on "Submit" is the ordinary way a support queue fills with duplicates.
Case.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "accounts/", "case": { "product": "", "feature": "", "title": "", "description": "" } }' \ api.plany.cloud:443 plany.support.v1.SupportService/CreateCase{ "name": "accounts//cases/my-case", "uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11", "number": "0", "product": "", "feature": "", "resource": "", "title": "", "description": "", "state": "STATE_OPEN", "state_reason": "", "closed_by": "AUTHOR_TYPE_CUSTOMER", "priority": "PRIORITY_NORMAL", "plan": "", "escalated": false, "agent_display_name": "", "waiting_on": "WAITING_ON_SUPPORT", "creator": "", "creator_email": "", "message_count": 0, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "last_message_time": "2026-10-04T12:00:00Z", "resolve_time": "2026-10-04T12:00:00Z", "close_time": "2026-10-04T12:00:00Z", "escalate_time": "2026-10-04T12:00:00Z"}grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "accounts//cases/my-case" }' \ api.plany.cloud:443 plany.support.v1.SupportService/GetCase{ "name": "accounts//cases/my-case", "uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11", "number": "0", "product": "", "feature": "", "resource": "", "title": "", "description": "", "state": "STATE_OPEN", "state_reason": "", "closed_by": "AUTHOR_TYPE_CUSTOMER", "priority": "PRIORITY_NORMAL", "plan": "", "escalated": false, "agent_display_name": "", "waiting_on": "WAITING_ON_SUPPORT", "creator": "", "creator_email": "", "message_count": 0, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "last_message_time": "2026-10-04T12:00:00Z", "resolve_time": "2026-10-04T12:00:00Z", "close_time": "2026-10-04T12:00:00Z", "escalate_time": "2026-10-04T12:00:00Z"}Lists an account's cases, most recent activity first.
parentstringRequiredaccounts/{account}, or accounts/- for an internal caller.
page_sizeint32page_tokenstringstatesrepeated Case.StateOnly cases in these states. Empty means all of them.
STATE_OPENSTATE_RESOLVEDSTATE_CLOSEDgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "accounts/", "page_size": 50 }' \ api.plany.cloud:443 plany.support.v1.SupportService/ListCases{ "cases": [ { "name": "accounts//cases/my-case", "uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11", "number": "0", "product": "", "feature": "", "resource": "", "title": "", "description": "", "state": "STATE_OPEN", "state_reason": "", "closed_by": "AUTHOR_TYPE_CUSTOMER", "priority": "PRIORITY_NORMAL", "plan": "", "escalated": false, "agent_display_name": "", "waiting_on": "WAITING_ON_SUPPORT", "creator": "", "creator_email": "", "message_count": 0, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "last_message_time": "2026-10-04T12:00:00Z", "resolve_time": "2026-10-04T12:00:00Z", "close_time": "2026-10-04T12:00:00Z", "escalate_time": "2026-10-04T12:00:00Z" } ], "next_page_token": ""}The timeline, oldest first.
parentstringRequiredaccounts/{account}/cases/{case}.
page_sizeint32page_tokenstringgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "accounts//cases/my-case", "page_size": 50 }' \ api.plany.cloud:443 plany.support.v1.SupportService/ListCaseMessages{ "messages": [ { "name": "accounts//cases/my-case/messages/my-message", "author_type": "AUTHOR_TYPE_CUSTOMER", "author_display_name": "", "body": "", "attachments": [ { "name": "accounts//attachments/my-attachment", "filename": "", "content_type": "", "size_bytes": "0", "state": "STATE_PENDING", "create_time": "2026-10-04T12:00:00Z" } ], "create_time": "2026-10-04T12:00:00Z" } ], "next_page_token": ""}The customer writes on a case.
On a resolved case this reopens it. On a closed one it is refused with FAILED_PRECONDITION, and the customer opens a new case.
parentstringRequiredaccounts/{account}/cases/{case}.
bodystringRequiredPlain text. Markdown is not rendered by either end, so nothing a customer writes can turn into a link that does not look like one.
namestringIdentifieraccounts/{account}/cases/{case}/messages/{message}.
attachmentsrepeated stringUploaded attachments, by name.
request_idstringMakes a retried send return the message the first attempt stored.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "accounts//cases/my-case", "message": { "body": "" } }' \ api.plany.cloud:443 plany.support.v1.SupportService/CreateCaseMessage{ "name": "accounts//cases/my-case/messages/my-message", "author_type": "AUTHOR_TYPE_CUSTOMER", "author_display_name": "", "body": "", "attachments": [ { "name": "accounts//attachments/my-attachment", "filename": "", "content_type": "", "size_bytes": "0", "state": "STATE_PENDING", "create_time": "2026-10-04T12:00:00Z" } ], "create_time": "2026-10-04T12:00:00Z"}Closes a case — "I don't need this any more". A person at Plany may also close a case once it is finished.
namestringRequiredreasonstringWhy. Optional.
Case.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "accounts//cases/my-case" }' \ api.plany.cloud:443 plany.support.v1.SupportService/CloseCase{ "name": "accounts//cases/my-case", "uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11", "number": "0", "product": "", "feature": "", "resource": "", "title": "", "description": "", "state": "STATE_OPEN", "state_reason": "", "closed_by": "AUTHOR_TYPE_CUSTOMER", "priority": "PRIORITY_NORMAL", "plan": "", "escalated": false, "agent_display_name": "", "waiting_on": "WAITING_ON_SUPPORT", "creator": "", "creator_email": "", "message_count": 0, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "last_message_time": "2026-10-04T12:00:00Z", "resolve_time": "2026-10-04T12:00:00Z", "close_time": "2026-10-04T12:00:00Z", "escalate_time": "2026-10-04T12:00:00Z"}Attachment is a file added to a case: a screenshot, a recording, a crash log.
Uploaded before it is attached: CreateAttachment returns a URL to upload the file to directly, then the attachment is named when writing on the case.
filenamestringRequiredThe file's own name, as the customer's machine called it. Shown, never used as a path.
content_typestringRequiredImages, video, plain text, logs, JSON, PDF or zip. Anything else is refused when the upload is requested.
size_bytesint64RequiredWhat the client said it would upload; once attached, what actually arrived.
namestringIdentifieraccounts/{account}/attachments/{attachment}.
STATE_PENDINGSTATE_ATTACHED{ "name": "accounts//attachments/my-attachment", "filename": "", "content_type": "", "size_bytes": "0", "state": "STATE_PENDING", "create_time": "2026-10-04T12:00:00Z"}SupportFeature is one part of a product.
idstringconnectivity. Unique within its product; other is in every product.
display_namestring"Players can't connect".
{ "id": "", "display_name": "My server"}SupportTopic is a product a case can be about, and its parts.
The catalogue is served rather than written into each client, so the
console's picker, the CLI's validation and the bot's thread titles cannot
disagree about what playpute/connectivity means.
idstringplaypute, billing, other.
display_namestring"Game servers".
descriptionstringOne sentence, shown under the product in a picker.
featuresrepeated SupportFeatureidstringconnectivity. Unique within its product; other is in every product.
display_namestring"Players can't connect".
resource_typesrepeated stringThe resource types a case about this product may name, as
google.api.resource types — playpute.plany.cloud/GameServer. Empty
when the product has nothing a case would point at, like billing.
{ "id": "", "display_name": "My server", "description": "", "features": [ { "id": "", "display_name": "My server" } ], "resource_types": [ "" ]}Case is one support conversation.
productstringRequiredImmutableThe product the case is about, as a SupportTopic.id — playpute,
sandbox, billing, other.
featurestringRequiredImmutableThe part of the product, as a SupportFeature.id within it. other is
always valid.
titlestringRequiredOne line, written by the customer. At most 140 characters.
descriptionstringRequiredImmutableWhat the customer wrote when they opened the case. Also the first message on the timeline, with the attachments that came with it; kept here so a list can show it without reading the timeline.
namestringIdentifieraccounts/{account}/cases/{case}.
uidstringOutput onlynumberint64Output onlyA short number for a person to say out loud: "case 1042". Unique across the platform and assigned in order, so it also tells somebody roughly how old a case is.
resourcestringImmutableThe one resource the case is about, if there is one — projects/p/locations/eu-west-2/gameServers/survival.
Optional, and the most useful field on the case when set: it lets the person answering see that server's state and recent logs straight away rather than asking which one you mean. Must belong to the case's account.
STATE_OPENSTATE_RESOLVEDSTATE_CLOSEDstate_reasonstringOutput onlyThe resolution for STATE_RESOLVED, the reason for STATE_CLOSED. Empty
while open.
Who closed it. Unset unless the case is closed.
AUTHOR_TYPE_CUSTOMERAUTHOR_TYPE_STAFFAUTHOR_TYPE_SYSTEMPRIORITY_NORMALPRIORITY_HIGHplanstringOutput onlyThe plan the account was on when the case was opened, which is where
priority came from. plans/pro.
escalatedboolOutput onlyHanded to a specialist. Orthogonal to state: an escalated case is still
open, and is still resolved or closed in the ordinary way.
agent_display_namestringOutput onlyThe name every staff message on this case is signed with, e.g. "Juniper". Generated when the case is opened and never changed.
WAITING_ON_SUPPORTWAITING_ON_CUSTOMERcreatorstringOutput onlyusers/{user}, or the API key or service account that opened it.
creator_emailstringOutput onlyWhere replies are emailed. The creator's verified address when a person opened the case; empty when a credential with no person behind it did, and the account's own contact is written to instead.
message_countint32Output onlyThe newest message, from anybody including the platform.
CaseMessage is one entry on a case's timeline. Immutable.
bodystringRequiredPlain text. Markdown is not rendered by either end, so nothing a customer writes can turn into a link that does not look like one.
namestringIdentifieraccounts/{account}/cases/{case}/messages/{message}.
AUTHOR_TYPE_CUSTOMERAUTHOR_TYPE_STAFFAUTHOR_TYPE_SYSTEMauthor_display_namestringOutput onlyWhat to show beside it. The case's alias for staff, the person's own name for a customer, "Plany" for the platform.
filenamestringRequiredThe file's own name, as the customer's machine called it. Shown, never used as a path.
content_typestringRequiredImages, video, plain text, logs, JSON, PDF or zip. Anything else is refused when the upload is requested.
size_bytesint64RequiredWhat the client said it would upload; once attached, what actually arrived.
namestringIdentifieraccounts/{account}/attachments/{attachment}.
STATE_PENDINGSTATE_ATTACHEDattachmentAttachmentfilenamestringRequiredThe file's own name, as the customer's machine called it. Shown, never used as a path.
content_typestringRequiredImages, video, plain text, logs, JSON, PDF or zip. Anything else is refused when the upload is requested.
size_bytesint64RequiredWhat the client said it would upload; once attached, what actually arrived.
namestringIdentifieraccounts/{account}/attachments/{attachment}.
STATE_PENDINGSTATE_ATTACHEDupload_urlstringPUT the file's bytes here.
upload_headersmap<string, string>Headers the PUT must carry, exactly. They are signed into the URL, so a missing one is a 403 rather than a file stored without it.
expire_timeTimestampurlstringexpire_timeTimestampmessagesrepeated CaseMessagebodystringRequiredPlain text. Markdown is not rendered by either end, so nothing a customer writes can turn into a link that does not look like one.
namestringIdentifieraccounts/{account}/cases/{case}/messages/{message}.
AUTHOR_TYPE_CUSTOMERAUTHOR_TYPE_STAFFAUTHOR_TYPE_SYSTEMauthor_display_namestringOutput onlyWhat to show beside it. The case's alias for staff, the person's own name for a customer, "Plany" for the platform.
filenamestringRequiredThe file's own name, as the customer's machine called it. Shown, never used as a path.
content_typestringRequiredImages, video, plain text, logs, JSON, PDF or zip. Anything else is refused when the upload is requested.
size_bytesint64RequiredWhat the client said it would upload; once attached, what actually arrived.
namestringIdentifieraccounts/{account}/attachments/{attachment}.
STATE_PENDINGSTATE_ATTACHEDnext_page_tokenstringcasesrepeated CaseproductstringRequiredImmutableThe product the case is about, as a SupportTopic.id — playpute,
sandbox, billing, other.
featurestringRequiredImmutableThe part of the product, as a SupportFeature.id within it. other is
always valid.
titlestringRequiredOne line, written by the customer. At most 140 characters.
descriptionstringRequiredImmutableWhat the customer wrote when they opened the case. Also the first message on the timeline, with the attachments that came with it; kept here so a list can show it without reading the timeline.
namestringIdentifieraccounts/{account}/cases/{case}.
uidstringOutput onlynumberint64Output onlyA short number for a person to say out loud: "case 1042". Unique across the platform and assigned in order, so it also tells somebody roughly how old a case is.
resourcestringImmutableThe one resource the case is about, if there is one — projects/p/locations/eu-west-2/gameServers/survival.
Optional, and the most useful field on the case when set: it lets the person answering see that server's state and recent logs straight away rather than asking which one you mean. Must belong to the case's account.
STATE_OPENSTATE_RESOLVEDSTATE_CLOSEDstate_reasonstringOutput onlyThe resolution for STATE_RESOLVED, the reason for STATE_CLOSED. Empty
while open.
Who closed it. Unset unless the case is closed.
AUTHOR_TYPE_CUSTOMERAUTHOR_TYPE_STAFFAUTHOR_TYPE_SYSTEMPRIORITY_NORMALPRIORITY_HIGHplanstringOutput onlyThe plan the account was on when the case was opened, which is where
priority came from. plans/pro.
escalatedboolOutput onlyHanded to a specialist. Orthogonal to state: an escalated case is still
open, and is still resolved or closed in the ordinary way.
agent_display_namestringOutput onlyThe name every staff message on this case is signed with, e.g. "Juniper". Generated when the case is opened and never changed.
WAITING_ON_SUPPORTWAITING_ON_CUSTOMERcreatorstringOutput onlyusers/{user}, or the API key or service account that opened it.
creator_emailstringOutput onlyWhere replies are emailed. The creator's verified address when a person opened the case; empty when a credential with no person behind it did, and the account's own contact is written to instead.
message_countint32Output onlyThe newest message, from anybody including the platform.
next_page_tokenstringtopicsrepeated SupportTopicidstringplaypute, billing, other.
display_namestring"Game servers".
descriptionstringOne sentence, shown under the product in a picker.
featuresrepeated SupportFeatureidstringconnectivity. Unique within its product; other is in every product.
display_namestring"Players can't connect".
resource_typesrepeated stringThe resource types a case about this product may name, as
google.api.resource types — playpute.plany.cloud/GameServer. Empty
when the product has nothing a case would point at, like billing.
| Value | Description |
|---|---|
| STATE_UNSPECIFIED | |
| STATE_PENDING | A URL has been issued and nothing has been confirmed to have arrived. |
| STATE_ATTACHED | Attached to a message, with the bytes confirmed present. |
| Value | Description |
|---|---|
| PRIORITY_UNSPECIFIED | |
| PRIORITY_NORMAL | |
| PRIORITY_HIGH | An account on the Pro plan when the case was opened. |
| Value | Description |
|---|---|
| STATE_UNSPECIFIED | |
| STATE_OPEN | Somebody is expected to say something. Who is `waiting_on`. |
| STATE_RESOLVED | Plany believes the problem is solved. `state_reason` is the resolution. Not terminal: a customer replying to a resolved case reopens it, because "that did not fix it" is the most common reply a resolution gets and making somebody open a second case to say it loses the first one's history. |
| STATE_CLOSED | Finished. Terminal: a closed case refuses new messages, and the customer opens a new case instead. `state_reason` says why it closed. |
Who has to speak next, derived from the last message that was not the platform's own.
| Value | Description |
|---|---|
| WAITING_ON_UNSPECIFIED | |
| WAITING_ON_SUPPORT | The customer wrote last, or nobody at Plany has answered yet. |
| WAITING_ON_CUSTOMER | Plany wrote last. |