Groundfloor Docs

Support API

Open, list, reply to, resolve and close your organization's support requests.

Help & support in the Client Portal reads these routes. Each request (a ticket in the API) belongs to an organization.

GET  /v1/organizations/{organization_id}/support/tickets
POST /v1/organizations/{organization_id}/support/tickets
GET  /v1/organizations/{organization_id}/support/tickets/{ticket_id}
POST /v1/organizations/{organization_id}/support/tickets/{ticket_id}/messages
POST /v1/organizations/{organization_id}/support/tickets/{ticket_id}/resolve
POST /v1/organizations/{organization_id}/support/tickets/{ticket_id}/close

There is no /v1/accounts/... path for these routes.

See also: Support

Who can call them

A signed-in team member's token, such as the one from gf token. The caller needs read on the organization and an accepted organization role of Owner, Admin, Billing admin, Writer, Member or Partner.

M2M tokens, workload identity tokens and app user tokens get 403 on every route.

Every route returns only the tickets the caller can see, in the path's organization:

  • the caller's own tickets;
  • for Owners, every ticket;
  • for Admins, every ticket except security;
  • for Billing admins, billing tickets.

A ticket the caller can't see, or one in another organization, returns 404. Anyone who can see a ticket can reply to, resolve or close it.

Responses carry Cache-Control: no-store.

curl -s -H "Authorization: Bearer $TOKEN" \
  "https://platform.groundfloor.cloud/v1/organizations/$ORGANIZATION_ID/support/tickets?status=open&status=waiting_on_customer" | jq .

List tickets

GET /v1/organizations/{organization_id}/support/tickets

Most recently updated first.

Query parameterDefaultDescription
statusallopen, waiting_on_groundfloor, waiting_on_customer, resolved or closed. Repeat it for several: ?status=open&status=waiting_on_customer
categoryallbilling, technical, account, security or other
limit25Tickets per page, 1 to 100
offset0Tickets to skip, up to 100,000
{
  "items": [
    {
      "id": "…",
      "number": 1234,
      "subject": "Can't connect to my database",
      "category": "technical",
      "priority": "normal",
      "status": "waiting_on_customer",
      "workspace_id": "…",
      "requester_user_id": "…",
      "requester_name": "Ada Lovelace",
      "created_at": "2026-10-14T09:00:00Z",
      "updated_at": "2026-10-14T10:30:00Z",
      "resolved_at": null,
      "closed_at": null,
      "last_customer_activity_at": "2026-10-14T09:00:00Z",
      "last_operator_activity_at": "2026-10-14T10:30:00Z"
    }
  ],
  "total_count": 1,
  "limit": 25,
  "offset": 0
}

number is the reference people see, such as #1234. Routes look tickets up by id only, never by number. last_operator_activity_at is the last time Groundfloor acted on the ticket.

Create a ticket

POST /v1/organizations/{organization_id}/support/tickets
{
  "subject": "Can't connect to my database",
  "category": "technical",
  "priority": "high",
  "workspace_id": "…",
  "description": "Since 09:00 every query from my Service times out…",
  "context": {
    "error_code": "database_provisioning_failed",
    "error_text": "Database provisioning failed.",
    "page_path": "/databases/…",
    "workspace_id": "…",
    "resource_id": "…"
  }
}
FieldRequiredDescription
subjectYes1 to 200 characters, one line
categoryYesbilling, technical, account, security or other. Sets who can see the ticket
priorityNolow, normal (default), high or urgent. Use urgent only when production is down. Groundfloor may change it
workspace_idNoA workspace the ticket is about. It must belong to the organization, or 422
descriptionYesPlain text, up to 20,000 characters. Becomes the first message
contextNoWhere the problem happened. Only the keys below are accepted

context keys; anything else, org_id included, returns 422:

KeyDescription
error_codeA short code for what failed, up to 100 characters
error_textThe error shown, up to 500 characters
page_pathThe Client Portal page, as a path only: it must start with a single /, with no scheme or host and no spaces. //… and /\… are refused. Up to 500 characters
workspace_idMust belong to the organization, like the top-level field
resource_idThe ID of the item involved, up to 256 characters

Context values are single lines.

All text is stored as plain text: nothing is rendered as HTML or Markdown. Control characters other than line breaks and tabs, and invisible direction-override characters, are removed before saving.

Returns 201 with the full ticket (as in Get a ticket) plus warnings:

{
  "id": "…",
  "number": 1234,
  "status": "open",
  "messages": [ { "id": "…", "author_kind": "customer", "author_name": "Ada Lovelace", "body": "Since 09:00…", "redacted": false, "created_at": "…" } ],
  "warnings": ["possible_secret"]
}

warnings contains possible_secret when the subject, description or a context value looks like a credential, such as a private key, a JWT, a client secret or an access key. The ticket is created anyway. Rotate the credential, and ask Groundfloor to redact it; see If you shared a secret by mistake. Otherwise warnings is [].

Get a ticket

GET /v1/organizations/{organization_id}/support/tickets/{ticket_id}

Returns the list fields plus context and messages, oldest first:

Message fieldDescription
author_kindcustomer, operator (Groundfloor) or system
author_nameThe team member's name for customer, Groundfloor Support for operator, null for system
bodyPlain text. [redacted by Groundfloor] when redacted
redactedtrue if Groundfloor redacted the message

A redacted subject reads [redacted by Groundfloor] too.

Reply

POST /v1/organizations/{organization_id}/support/tickets/{ticket_id}/messages
{ "body": "It started again at 14:00." }

body is plain text, up to 20,000 characters. Returns 200 with the ticket and warnings, as for create.

A reply moves the ticket to waiting_on_groundfloor, or to open if Groundfloor hasn't replied yet. A reply to a resolved ticket reopens it the same way: open if Groundfloor never replied, otherwise waiting_on_groundfloor, unless the organization already has the maximum number of open tickets (429). A reply to a closed ticket returns 409.

Resolve or close

POST /v1/organizations/{organization_id}/support/tickets/{ticket_id}/resolve
POST /v1/organizations/{organization_id}/support/tickets/{ticket_id}/close

No body. Both return 200 with the ticket and add a system message ("Marked as resolved by the customer." or "Closed by the customer.").

  • Resolve sets resolved. A later reply reopens it. Resolving a resolved ticket changes nothing; resolving a closed ticket returns 409.
  • Close sets closed, which is final: no more replies or status changes. Closing a closed ticket changes nothing. To follow up, create a new ticket and mention the number.

Statuses

statusMeans
openGroundfloor hasn't replied yet, including a resolved ticket reopened before any answer
waiting_on_customerGroundfloor replied
waiting_on_groundfloorYou replied after Groundfloor's answer, or reopened a resolved ticket Groundfloor had answered
resolvedMarked resolved by you or Groundfloor
closedFinal

Limits

LimitDefault
New tickets per person5 an hour
Replies per person60 an hour
New tickets per organization20 in 24 hours
Tickets per organization that aren't resolved or closed25 at once
Create and reply, per client address30 a minute

Each returns 429 with a sentence saying which limit was reached.

Errors

StatusWhen
403An M2M, workload identity or app user token; no read on the organization; or no accepted organization role that can use support
404The organization doesn't exist or was deleted; the ticket doesn't exist, is in another organization, or the caller can't see it
409Reply to, or resolve, a closed ticket
422A field failed validation: an empty or too long subject or body, an unknown category or priority, an unknown field or context key, a page_path that isn't a path, or a workspace_id outside the organization
429A limit was reached