Developer API

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.

Header
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 401 immediately.

Quick start

Request
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:

HeaderMeaning
X-RateLimit-LimitMax requests per window (60)
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetUnix timestamp (seconds) when the window resets
Retry-AfterOnly on 429 - seconds to wait before retrying
429 response
{
  "error": "Rate limit exceeded. Maximum 60 requests per minute per API key.",
  "code": "RATE_LIMITED"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, invalid, expired, or revoked key
403FORBIDDENKey missing a required scope
404NOT_FOUNDTicket / transcript / guild not found
429RATE_LIMITEDOver 60 requests/minute for this key
500INTERNAL_ERRORUnexpected server error

Endpoints

GET/api/v1/tickets

List tickets for the key's guild. Default status=open.

  • status - open, claimed, closed, or all
  • ticketNumber / ticketId
  • userId, channelId
  • panel / categoryName
  • page, limit (max 100, default 25)
Request
curl "https://ticketsbot.org/api/v1/tickets?status=all&limit=2" \
  -H "Authorization: Bearer tb_live_..."
Response 200
{
  "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.

Request
curl "https://ticketsbot.org/api/v1/tickets/52" \
  -H "Authorization: Bearer tb_live_..."
Response 200
{
  "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.

Request
curl "https://ticketsbot.org/api/v1/transcripts?limit=2" \
  -H "Authorization: Bearer tb_live_..."
Response 200
{
  "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.

Request (HTML)
curl "https://ticketsbot.org/api/v1/transcripts/52" \
  -H "Authorization: Bearer tb_live_..."
Response 200 (HTML)
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html lang="en">
<head>...</head>
<body>...ticket conversation...</body>
</html>
Request (JSON)
curl "https://ticketsbot.org/api/v1/transcripts/52?format=json" \
  -H "Authorization: Bearer tb_live_..."
Response 200 (JSON)
{
  "ticketNumber": 52,
  "guildId": "1516633159641468979",
  "source": "r2",
  "isArchived": false,
  "html": "<!DOCTYPE html>..."
}

GET/api/v1/panels

Ticket panels / categories for the guild.

Request
curl "https://ticketsbot.org/api/v1/panels" \
  -H "Authorization: Bearer tb_live_..."
Response 200
{
  "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.

Request
curl "https://ticketsbot.org/api/v1/tags" \
  -H "Authorization: Bearer tb_live_..."
Response 200
{
  "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.

Request
curl "https://ticketsbot.org/api/v1/teams" \
  -H "Authorization: Bearer tb_live_..."
Response 200
{
  "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).

Request
curl "https://ticketsbot.org/api/v1/blacklist" \
  -H "Authorization: Bearer tb_live_..."
Response 200
{
  "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).

Request
curl "https://ticketsbot.org/api/v1/guild" \
  -H "Authorization: Bearer tb_live_..."
Response 200
{
  "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

FieldDescription
ticketNumberHuman-facing ticket number
channelIdDiscord channel or thread ID
userIdDiscord ID of the opener
userNameUsername snapshot when available
categoryNamePanel / category name
claimedByStaff Discord ID, or null
statusopen, claimed, or closed
createdAt / closedAt / ...ISO-8601 timestamps
closeReasonClose reason when set
rating1-5 user rating when enabled
hasTranscriptWhether 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.

Still stuck? The support team can point you in the right direction.

Get support