Skip to content

API reference

Free for every server. Rate limited per key so one integration cannot starve the others.

Authentication

Create a key under Dashboard → Integrations. Keys are scoped to one server, shown once, and stored only as a hash.

curl https://YOUR_TICKETCREW_DOMAIN/api/v1/tickets \
  -H "Authorization: Bearer tb_live_your_key_here"

Endpoints

GET/api/v1/ticketstickets:read

Search tickets. Supports the same filters as the dashboard queue.

Query: q, status, priority, categoryId, assignedTo, tag, from, to, limit, cursor

GET/api/v1/tickets/{id}tickets:read

One ticket with its answers, tags, participants and rating.

POST/api/v1/tickets/{id}/closetickets:write

Close a ticket and generate its transcript.

GET/api/v1/analyticsanalytics:read

Daily aggregates: volume, response times, satisfaction, SLA.

Query: range=7d|30d|90d|12m

Webhooks

TicketCrew POSTs a signed JSON body to your endpoint. Verify the X-TicketCrew-Signature header before trusting anything in it.

ticket.createdticket.claimedticket.closedticket.reopenedticket.updatedticket.ratedticket.messagesla.warningsla.breach

Payload

{
  "id": "evt_m3k2n1",
  "event": "ticket.closed",
  "guildId": "123456789012345678",
  "createdAt": "2026-08-31T10:24:00.000Z",
  "data": {
    "id": "clx8f2...",
    "number": 1284,
    "status": "CLOSED",
    "priority": "HIGH",
    "openerId": "987654321098765432",
    "assignedToName": "Nora",
    "transcriptId": "A7K2M9QX4B1C",
    "reason": "Resolved"
  }
}

Verifying a signature

The header is t=<unix_ms>,v1=<hex>. The signed payload is `${t}.${rawBody}` with HMAC-SHA256. Reject anything older than five minutes - the timestamp is inside the signature precisely so a captured request cannot be replayed later.

import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(',').map((s) => s.split('=')),
  );
  const timestamp = Number(parts.t);
  if (Math.abs(Date.now() - timestamp) > 5 * 60_000) return false;

  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1);
  return a.length === b.length && timingSafeEqual(a, b);
}

Errors

Every failure returns the same shape, so you can branch on error.code rather than parsing prose.

{ "error": { "code": "rate_limited", "message": "..." } }