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}/closeThere 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,
billingtickets.
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/ticketsMost recently updated first.
| Query parameter | Default | Description |
|---|---|---|
status | all | open, waiting_on_groundfloor, waiting_on_customer, resolved or closed. Repeat it for several: ?status=open&status=waiting_on_customer |
category | all | billing, technical, account, security or other |
limit | 25 | Tickets per page, 1 to 100 |
offset | 0 | Tickets 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": "…"
}
}| Field | Required | Description |
|---|---|---|
subject | Yes | 1 to 200 characters, one line |
category | Yes | billing, technical, account, security or other. Sets who can see the ticket |
priority | No | low, normal (default), high or urgent. Use urgent only when production is down. Groundfloor may change it |
workspace_id | No | A workspace the ticket is about. It must belong to the organization, or 422 |
description | Yes | Plain text, up to 20,000 characters. Becomes the first message |
context | No | Where the problem happened. Only the keys below are accepted |
context keys; anything else, org_id included, returns 422:
| Key | Description |
|---|---|
error_code | A short code for what failed, up to 100 characters |
error_text | The error shown, up to 500 characters |
page_path | The 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_id | Must belong to the organization, like the top-level field |
resource_id | The 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 field | Description |
|---|---|
author_kind | customer, operator (Groundfloor) or system |
author_name | The team member's name for customer, Groundfloor Support for operator, null for system |
body | Plain text. [redacted by Groundfloor] when redacted |
redacted | true 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}/closeNo 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 returns409. - 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
status | Means |
|---|---|
open | Groundfloor hasn't replied yet, including a resolved ticket reopened before any answer |
waiting_on_customer | Groundfloor replied |
waiting_on_groundfloor | You replied after Groundfloor's answer, or reopened a resolved ticket Groundfloor had answered |
resolved | Marked resolved by you or Groundfloor |
closed | Final |
Limits
| Limit | Default |
|---|---|
| New tickets per person | 5 an hour |
| Replies per person | 60 an hour |
| New tickets per organization | 20 in 24 hours |
Tickets per organization that aren't resolved or closed | 25 at once |
| Create and reply, per client address | 30 a minute |
Each returns 429 with a sentence saying which limit was reached.
Errors
| Status | When |
|---|---|
403 | An M2M, workload identity or app user token; no read on the organization; or no accepted organization role that can use support |
404 | The organization doesn't exist or was deleted; the ticket doesn't exist, is in another organization, or the caller can't see it |
409 | Reply to, or resolve, a closed ticket |
422 | A 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 |
429 | A limit was reached |
Related
- Support — the same requests in the Client Portal, who sees what, and the emails we send
- Organizations API
- Authentication