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
/api/v1/ticketstickets:readSearch tickets. Supports the same filters as the dashboard queue.
Query: q, status, priority, categoryId, assignedTo, tag, from, to, limit, cursor
/api/v1/tickets/{id}tickets:readOne ticket with its answers, tags, participants and rating.
/api/v1/tickets/{id}/closetickets:writeClose a ticket and generate its transcript.
/api/v1/analyticsanalytics:readDaily 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.breachPayload
{
"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": "..." } }