8. Developer API
The Developer API lets external tools read your server's ticket data with a guild-scoped API key. The endpoints currently are read-only. Closing, claiming, and messaging still happen in Discord or the web dashboard.
Base URL: https://ticketsbot.org/api/v1
Create and revoke keys in Dashboard → Settings → Developer API.
Authentication
Every request needs a Bearer token. Keys look like tb_live_... and are bound to one Discord server (guild). There is no guild ID in the URL - the key already scopes the request.
Authorization: Bearer tb_live_YOUR_KEY_HERE- The full secret is shown once when you generate it. TicketBot stores only a hash.
- Up to 5 active keys per server.
- Optional TTL: 7 days, 30 days, 90 days, 1 year, or never.
- Revoke anytime from Settings → Developer API. Revoked keys return
401immediately.
Quick start
curl "https://ticketsbot.org/api/v1/tickets?status=open&limit=10" \
-H "Authorization: Bearer tb_live_..."Rate limits
Each API key may make 60 requests per minute.
Every response includes rate-limit headers:
| Header | Meaning |
|---|---|
| X-RateLimit-Limit | Max requests per window (60) |
| X-RateLimit-Remaining | Requests left in the current window |
| X-RateLimit-Reset | Unix timestamp (seconds) when the window resets |
| Retry-After | Only on 429 - seconds to wait before retrying |
{
"error": "Rate limit exceeded. Maximum 60 requests per minute per API key.",
"code": "RATE_LIMITED"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, invalid, expired, or revoked key |
| 403 | FORBIDDEN | Key missing a required scope |
| 404 | NOT_FOUND | Ticket / transcript / guild not found |
| 429 | RATE_LIMITED | Over 60 requests/minute for this key |
| 500 | INTERNAL_ERROR | Unexpected server error |
Endpoints
GET/api/v1/tickets
List tickets for the key's guild. Default status=open.
status-open,claimed,closed, orallticketNumber/ticketIduserId,channelIdpanel/categoryNamepage,limit(max 100, default 25)
curl "https://ticketsbot.org/api/v1/tickets?status=all&limit=2" \
-H "Authorization: Bearer tb_live_..."{
"data": [
{
"ticketNumber": 52,
"channelId": "1540421010002677761",
"userId": "1157355571167641711",
"userName": "alice",
"categoryName": "support",
"claimedBy": null,
"status": "closed",
"createdAt": "2026-08-21T18:03:09.129Z",
"closedAt": "2026-08-21T18:07:07.970Z",
"claimedAt": null,
"firstResponseAt": null,
"lastActivityAt": "2026-08-21T18:03:09.129Z",
"lastStaffMessageAt": null,
"lastUserMessageAt": "2026-08-21T18:03:09.128Z",
"closeReason": "Resolved",
"rating": null,
"hasTranscript": true
}
],
"total": 31,
"page": 1,
"limit": 2
}GET/api/v1/tickets/{ticketNumber}
Single ticket detail (active first, then archived). Use this for ID mapping: ticketNumber ↔ channelId ↔ userId.
curl "https://ticketsbot.org/api/v1/tickets/52" \
-H "Authorization: Bearer tb_live_..."{
"ticketNumber": 52,
"channelId": "1540421010002677761",
"userId": "1157355571167641711",
"userName": "alice",
"categoryName": "support",
"claimedBy": null,
"status": "closed",
"createdAt": "2026-08-21T18:03:09.129Z",
"closedAt": "2026-08-21T18:07:07.970Z",
"claimedAt": null,
"firstResponseAt": null,
"lastActivityAt": "2026-08-21T18:03:09.129Z",
"lastStaffMessageAt": null,
"lastUserMessageAt": "2026-08-21T18:03:09.128Z",
"closeReason": "Resolved",
"rating": null,
"hasTranscript": true
}GET/api/v1/transcripts
Closed tickets that have a stored transcript. Filters: ticketNumber, userId, panel, page, limit.
curl "https://ticketsbot.org/api/v1/transcripts?limit=2" \
-H "Authorization: Bearer tb_live_..."{
"data": [
{
"ticketNumber": 52,
"channelId": "1540421010002677761",
"userId": "1157355571167641711",
"userName": "alice",
"categoryName": "support",
"status": "closed",
"closedAt": "2026-08-21T18:07:07.970Z",
"hasTranscript": true
}
],
"total": 12,
"page": 1,
"limit": 2
}GET/api/v1/transcripts/{ticketNumber}
Returns the transcript HTML by default. Pass ?format=json for a JSON wrapper.
curl "https://ticketsbot.org/api/v1/transcripts/52" \
-H "Authorization: Bearer tb_live_..."Content-Type: text/html; charset=utf-8
<!DOCTYPE html>
<html lang="en">
<head>...</head>
<body>...ticket conversation...</body>
</html>curl "https://ticketsbot.org/api/v1/transcripts/52?format=json" \
-H "Authorization: Bearer tb_live_..."{
"ticketNumber": 52,
"guildId": "1516633159641468979",
"source": "r2",
"isArchived": false,
"html": "<!DOCTYPE html>..."
}GET/api/v1/panels
Ticket panels / categories for the guild.
curl "https://ticketsbot.org/api/v1/panels" \
-H "Authorization: Bearer tb_live_..."{
"data": [
{
"id": 6,
"name": "support",
"description": "General support",
"emoji": "🎫",
"categoryId": "1516633159641468980",
"panelChannelId": "1516633160073347222",
"panelMessageId": "1539194392110436373",
"panelTitle": "Open a ticket",
"panelContent": "Click below for help",
"panelColor": "#560000",
"buttonText": "Create Ticket",
"buttonEmoji": "🎫",
"buttonColor": "primary",
"welcomeMessage": null,
"maxTicketsPerUser": null,
"modalEnabled": false,
"transcriptChannelId": null,
"supportTeamIds": [30],
"accessControl": [],
"createdAt": "2026-06-17T02:54:21.545Z"
}
]
}GET/api/v1/tags
Canned reply tags.
curl "https://ticketsbot.org/api/v1/tags" \
-H "Authorization: Bearer tb_live_..."{
"data": [
{
"id": 3,
"name": "welcome",
"content": "Thanks for opening a ticket!",
"useEmbed": false,
"embedTitle": null,
"embedDescription": null,
"usageCount": 0,
"createdAt": "2026-07-28T18:24:30.250Z",
"updatedAt": "2026-07-28T18:24:30.250Z"
}
]
}GET/api/v1/teams
Support teams and their Discord role members.
curl "https://ticketsbot.org/api/v1/teams" \
-H "Authorization: Bearer tb_live_..."{
"data": [
{
"id": 9,
"name": "Default Team",
"isDefault": true,
"createdAt": "2026-06-17T02:44:47.367Z",
"updatedAt": "2026-06-17T02:44:47.367Z",
"members": [
{
"roleId": "1516637752802676798",
"addedBy": "1157355571167641711",
"addedAt": "2026-06-17T02:57:25.473Z"
}
]
}
]
}GET/api/v1/blacklist
Active blacklist entries (expired ones are omitted).
curl "https://ticketsbot.org/api/v1/blacklist" \
-H "Authorization: Bearer tb_live_..."{
"data": [
{
"id": 1,
"userId": "123456789012345678",
"roleId": null,
"reason": "Spam",
"blacklistedBy": "1157355571167641711",
"createdAt": "2026-08-01T12:00:00.000Z",
"expiresAt": null
}
]
}GET/api/v1/guild
Sanitized guild settings snapshot (no secrets).
curl "https://ticketsbot.org/api/v1/guild" \
-H "Authorization: Bearer tb_live_..."{
"id": "1516633159641468979",
"ticketCounter": 52,
"maxTicketsPerUser": 2,
"allowUserClose": true,
"closeConfirmationEnabled": false,
"storeTranscripts": true,
"hideClaimButton": false,
"enableUserRating": true,
"threadModeEnabled": false,
"timezone": "UTC",
"language": null,
"createdAt": "2026-06-17T02:40:00.000Z",
"updatedAt": "2026-08-21T18:00:00.000Z"
}Ticket object fields
| Field | Description |
|---|---|
| ticketNumber | Human-facing ticket number |
| channelId | Discord channel or thread ID |
| userId | Discord ID of the opener |
| userName | Username snapshot when available |
| categoryName | Panel / category name |
| claimedBy | Staff Discord ID, or null |
| status | open, claimed, or closed |
| createdAt / closedAt / ... | ISO-8601 timestamps |
| closeReason | Close reason when set |
| rating | 1-5 user rating when enabled |
| hasTranscript | Whether a transcript file exists |
Security notes
- Treat API keys like passwords. Never commit them or put them in client-side JavaScript.
- If a key leaks, revoke it immediately and generate a new one.
- Keys only grant read access for that one guild - they cannot pivot to another server.
- Transcript HTML may contain user-generated content; sanitize if you render it in a browser.
Currently we only support read-only endpoints.