REST API v1 · Developers · API reference
Tickets API reference
All 4 Tickets endpoints of the BotGG REST API v1: scopes, parameters, fields, errors and ready-to-run curl, JavaScript and Python examples.
Developer docs
On this page
Endpoints
Paths are relative to https://console.botgg.app/api/v1. Send your API key as Authorization: Bearer botgg_…; IDs are strings.
| Endpoint | Scope | Description |
|---|---|---|
GET/guilds/{guildId}/tickets | tickets:read | List tickets |
GET/guilds/{guildId}/tickets/{ticketNo} | tickets:read | Get a ticket |
GET/guilds/{guildId}/tickets/{ticketNo}/transcript | tickets:read | Get a closed ticket’s transcript |
POST/guilds/{guildId}/tickets/{ticketNo}/close | tickets:write | Close a ticket |
Besides its own errors below, every endpoint can return these common ones:
List tickets
GET/guilds/{guildId}/tickets
Newest first. status=active = open + pending. Pass before=<nextCursor> for the next page.
Needs this feature enabled on the server: Tickets
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
status | query | string | No | One of open, pending, closed, active |
panelId | query | integer | No | at least 1 |
userId | query | string | No | Only tickets opened by this user Discord ID (string) |
limit | query | integer | No | 1–100 · Default 25 |
before | query | integer | No | 1–999,999,999 |
Response
| Field | Type | Required | Description |
|---|---|---|---|
items | array of object | Yes | |
items[].ticketNo | integer | Yes | |
items[].status | string | Yes | One of open, pending, closed |
items[].userId | string | Yes | The opener |
items[].panelId | integer | null | Yes | |
items[].subject | string | null | Yes | |
items[].source | string | null | Yes | One of panel, command, context_menu, dashboard |
items[].isThread | boolean | Yes | |
items[].channelId | string | null | Yes | |
items[].claimedBy | string | null | Yes | |
items[].openedAt | string | null | Yes | ISO 8601 date-time |
items[].lastMessageAt | string | null | Yes | ISO 8601 date-time |
items[].closedAt | string | null | Yes | ISO 8601 date-time |
items[].closedBy | string | null | Yes | null = closed by the bot (autoclose, API, …) |
items[].closeReason | string | null | Yes | |
items[].closeType | string | null | Yes | One of manual, autoclose, user_leave, request, channel_deleted, thread_archived |
items[].messageCount | object | Yes | |
items[].messageCount.user | integer | Yes | |
items[].messageCount.staff | integer | Yes | |
items[].rating | integer | null | Yes | |
items[].hasTranscript | boolean | Yes | |
nextCursor | string | null | Yes |
{
"items": [
{
"ticketNo": 10,
"status": "open",
"userId": "876543210987654321",
"panelId": 98765,
"subject": "Help with my order",
"source": "panel",
"isThread": true,
"channelId": "234567890123456789",
"claimedBy": "example",
"openedAt": "2026-10-07T12:00:00.000Z",
"lastMessageAt": "2026-10-07T12:00:00.000Z",
"closedAt": "2026-10-07T12:00:00.000Z",
"closedBy": "example",
"closeReason": "example",
"closeType": "manual",
"messageCount": {
"user": 10,
"staff": 10
},
"rating": 10,
"hasTranscript": true
}
],
"nextCursor": "98700"
}Examples
curl -s "https://console.botgg.app/api/v1/guilds/123456789012345678/tickets?limit=25" \
-H "Authorization: Bearer $BOTGG_API_KEY"// Node.js 18+ (built-in fetch) — on your server, never in a browser
const res = await fetch('https://console.botgg.app/api/v1/guilds/123456789012345678/tickets?limit=25', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.BOTGG_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error}: ${data.message}`);
console.log(data);import os
import requests
res = requests.get(
"https://console.botgg.app/api/v1/guilds/123456789012345678/tickets",
headers={
"Authorization": f"Bearer {os.environ['BOTGG_API_KEY']}",
},
params={
"limit": 25,
},
timeout=10,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {data['error']}: {data['message']}")
print(data)Errors
| HTTP | Codes |
|---|---|
| 409 | FEATURE_DISABLED |
| 503 | SERVICE_UNAVAILABLE |
Get a ticket
GET/guilds/{guildId}/tickets/{ticketNo}
Includes the form answers, added members and a pending close request.
Needs this feature enabled on the server: Tickets
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
ticketNo | path | string | Yes |
Response
| Field | Type | Required | Description |
|---|---|---|---|
ticketNo | integer | Yes | |
status | string | Yes | One of open, pending, closed |
userId | string | Yes | The opener |
panelId | integer | null | Yes | |
subject | string | null | Yes | |
source | string | null | Yes | One of panel, command, context_menu, dashboard |
isThread | boolean | Yes | |
channelId | string | null | Yes | |
claimedBy | string | null | Yes | |
openedAt | string | null | Yes | ISO 8601 date-time |
lastMessageAt | string | null | Yes | ISO 8601 date-time |
closedAt | string | null | Yes | ISO 8601 date-time |
closedBy | string | null | Yes | null = closed by the bot (autoclose, API, …) |
closeReason | string | null | Yes | |
closeType | string | null | Yes | One of manual, autoclose, user_leave, request, channel_deleted, thread_archived |
messageCount | object | Yes | |
messageCount.user | integer | Yes | |
messageCount.staff | integer | Yes | |
rating | integer | null | Yes | |
hasTranscript | boolean | Yes | |
panelTitle | string | null | Yes | |
firstResponseAt | string | null | Yes | ISO 8601 date-time |
firstResponseBy | string | null | Yes | |
answers | array of object | Yes | Form answers given when opening |
answers[].fieldId | string | null | Yes | |
answers[].label | string | Yes | |
answers[].value | string | Yes | |
members | array of object | Yes | Users / roles added to the ticket |
members[].targetId | string | Yes | |
members[].type | string | Yes | One of user, role |
closeRequest | object | null | Yes | |
closeRequest.reason | string | null | Yes | |
closeRequest.closeAt | string | null | Yes | ISO 8601 date-time |
closeRequest.viaApi | boolean | Yes |
{
"ticketNo": 10,
"status": "open",
"userId": "876543210987654321",
"panelId": 98765,
"subject": "Help with my order",
"source": "panel",
"isThread": true,
"channelId": "234567890123456789",
"claimedBy": "example",
"openedAt": "2026-10-07T12:00:00.000Z",
"lastMessageAt": "2026-10-07T12:00:00.000Z",
"closedAt": "2026-10-07T12:00:00.000Z",
"closedBy": "example",
"closeReason": "example",
"closeType": "manual",
"messageCount": {
"user": 10,
"staff": 10
},
"rating": 10,
"hasTranscript": true,
"panelTitle": "example",
"firstResponseAt": "2026-10-07T12:00:00.000Z",
"firstResponseBy": "example",
"answers": [
{
"fieldId": "234567890123456789",
"label": "Example",
"value": "example"
}
],
"members": [
{
"targetId": "234567890123456789",
"type": "user"
}
],
"closeRequest": {
"reason": "Tournament prize",
"closeAt": "2026-10-07T12:00:00.000Z",
"viaApi": true
}
}Examples
curl -s "https://console.botgg.app/api/v1/guilds/123456789012345678/tickets/234567890123456789" \
-H "Authorization: Bearer $BOTGG_API_KEY"// Node.js 18+ (built-in fetch) — on your server, never in a browser
const res = await fetch('https://console.botgg.app/api/v1/guilds/123456789012345678/tickets/234567890123456789', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.BOTGG_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error}: ${data.message}`);
console.log(data);import os
import requests
res = requests.get(
"https://console.botgg.app/api/v1/guilds/123456789012345678/tickets/234567890123456789",
headers={
"Authorization": f"Bearer {os.environ['BOTGG_API_KEY']}",
},
timeout=10,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {data['error']}: {data['message']}")
print(data)Errors
| HTTP | Codes |
|---|---|
| 404 | TICKET_NOT_FOUND |
| 409 | FEATURE_DISABLED |
| 503 | SERVICE_UNAVAILABLE |
Get a closed ticket’s transcript
GET/guilds/{guildId}/tickets/{ticketNo}/transcript
The messages saved when the ticket was closed, oldest first, as text: no avatars and no attachment links (only file name, size and type). Pass after=<nextCursor> for the next page. 404 TRANSCRIPT_NOT_FOUND for open tickets or when transcripts are off.
Needs this feature enabled on the server: Tickets
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
ticketNo | path | string | Yes | |
limit | query | integer | No | 1–500 · Default 100 |
after | query | string | No | A message id from nextCursorDiscord ID (string) |
Response
| Field | Type | Required | Description |
|---|---|---|---|
ticketNo | integer | Yes | |
generatedAt | string | null | Yes | ISO 8601 date-time |
messageContent | boolean | Yes | false: the bot ran without the Message Content intent, so other users’ text may be empty |
totalMessages | integer | Yes | |
users | object | Yes | |
messages | array of object | Yes | |
messages[].id | string | Yes | |
messages[].authorId | string | null | Yes | |
messages[].content | string | Yes | |
messages[].createdAt | string | Yes | ISO 8601 date-time |
messages[].editedAt | string | null | Yes | ISO 8601 date-time |
messages[].system | boolean | Yes | |
messages[].replyTo | string | null | Yes | |
messages[].embeds | array of object | Yes | |
messages[].embeds[].title | string | null | Yes | |
messages[].embeds[].description | string | null | Yes | |
messages[].embeds[].fields | array of object | Yes | |
messages[].embeds[].fields[].name | string | Yes | |
messages[].embeds[].fields[].value | string | Yes | |
messages[].attachments | array of object | Yes | |
messages[].attachments[].name | string | Yes | |
messages[].attachments[].size | integer | Yes | |
messages[].attachments[].contentType | string | null | Yes | |
messages[].stickers | array of string | Yes | |
nextCursor | string | null | Yes | Pass as after for the next page |
{
"ticketNo": 10,
"generatedAt": "2026-10-07T12:00:00.000Z",
"messageContent": true,
"totalMessages": 10,
"users": {},
"messages": [
{
"id": "example",
"authorId": "234567890123456789",
"content": "example",
"createdAt": "2026-10-07T12:00:00.000Z",
"editedAt": "2026-10-07T12:00:00.000Z",
"system": true,
"replyTo": "example",
"embeds": [
{
"title": "example",
"description": "example",
"fields": [
{}
]
}
],
"attachments": [
{
"name": "Example",
"size": 10,
"contentType": "example"
}
],
"stickers": [
"example"
]
}
],
"nextCursor": "98700"
}Examples
curl -s "https://console.botgg.app/api/v1/guilds/123456789012345678/tickets/234567890123456789/transcript?limit=100" \
-H "Authorization: Bearer $BOTGG_API_KEY"// Node.js 18+ (built-in fetch) — on your server, never in a browser
const res = await fetch('https://console.botgg.app/api/v1/guilds/123456789012345678/tickets/234567890123456789/transcript?limit=100', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.BOTGG_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error}: ${data.message}`);
console.log(data);import os
import requests
res = requests.get(
"https://console.botgg.app/api/v1/guilds/123456789012345678/tickets/234567890123456789/transcript",
headers={
"Authorization": f"Bearer {os.environ['BOTGG_API_KEY']}",
},
params={
"limit": 100,
},
timeout=10,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {data['error']}: {data['message']}")
print(data)Errors
| HTTP | Codes |
|---|---|
| 404 | TICKET_NOT_FOUND, TRANSCRIPT_NOT_FOUND |
| 409 | FEATURE_DISABLED |
| 503 | SERVICE_UNAVAILABLE |
Close a ticket
POST/guilds/{guildId}/tickets/{ticketNo}/close
The bot closes it within about a minute exactly like a staff close: transcript (if enabled for the panel), transcript-channel log, DM to the opener, channel deleted / thread archived. The reason is shown as "<reason> (via API: <key name>)". Written to the audit log.
Needs this feature enabled on the server: Tickets
| Name | In | Type | Required | Description |
|---|---|---|---|---|
guildId | path | string | Yes | Your server’s ID · Discord ID (string) |
ticketNo | path | string | Yes | |
Idempotency-Key | header | string | No | Optional. Send a unique value (a UUID) and reuse it when you retry: the change happens only once. How it works |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | null | No | Shown in the close log and the opener’s DM up to 500 characters |
Response
| Field | Type | Required | Description |
|---|---|---|---|
ticketNo | integer | Yes | |
status | "closing" | Yes | |
reason | string | null | Yes | |
requestedAt | string | Yes | ISO 8601 date-time |
{
"ticketNo": 10,
"status": "closing",
"reason": "Tournament prize",
"requestedAt": "2026-10-07T12:00:00.000Z"
}Examples
curl -s -X POST "https://console.botgg.app/api/v1/guilds/123456789012345678/tickets/234567890123456789/close" \
-H "Authorization: Bearer $BOTGG_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"reason":"Tournament prize"}'// Node.js 18+ (built-in fetch) — on your server, never in a browser
const res = await fetch('https://console.botgg.app/api/v1/guilds/123456789012345678/tickets/234567890123456789/close', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BOTGG_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(), // reuse it when you retry this request
},
body: JSON.stringify({
reason: 'Tournament prize',
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error}: ${data.message}`);
console.log(data);import os, uuid
import requests
res = requests.post(
"https://console.botgg.app/api/v1/guilds/123456789012345678/tickets/234567890123456789/close",
headers={
"Authorization": f"Bearer {os.environ['BOTGG_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()), # reuse it when you retry this request
},
json={
"reason": "Tournament prize",
},
timeout=10,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {data['error']}: {data['message']}")
print(data)Errors
| HTTP | Codes |
|---|---|
| 400 | INVALID_JSON |
| 404 | TICKET_NOT_FOUND |
| 409 | FEATURE_DISABLED, TICKET_CLOSED |
| 413 | PAYLOAD_TOO_LARGE |
| 415 | UNSUPPORTED_MEDIA_TYPE |
| 422 | IDEMPOTENCY_KEY_REUSED |
| 503 | SERVICE_UNAVAILABLE |