API Reference
Build your own UI with the full REST API. Complete control over the experience.
Interactive API Documentation
Try API endpoints directly in your browser with our Swagger UI. Test requests, see responses, and explore the full API.
Open Swagger UI →Overview
The Dispatch Tickets API is a RESTful JSON API. All requests are made to:
https://api.dispatchtickets.com/v1Use the API when you want:
- Complete control over the UI/UX
- To integrate ticketing into an existing app
- Custom workflows that don't fit the portal model
- Backend-only integrations (no customer-facing UI)
RESTful
Predictable resource-oriented URLs
JSON
All requests and responses in JSON
API Key Auth
Bearer token authentication
TypeScript SDK
Type-safe client available
Authentication
All API requests require authentication via Bearer token:
Authorization: Bearer sk_live_your_api_keyGet your API key from the dashboard under Settings → API Keys.
Keep your API key secret. Never expose it in client-side code. All API calls should be made from your backend.
SDK Authentication
import { DispatchTickets } from '@dispatchtickets/sdk';
const dispatch = new DispatchTickets({
apiKey: process.env.DISPATCH_API_KEY!
});cURL Authentication
curl https://api.dispatchtickets.com/v1/brands \
-H "Authorization: Bearer sk_live_your_api_key"Brands
Brands (also called workspaces) are isolated containers for tickets. Each brand has its own customers, settings, and email identity.
List Brands
const { data: brands } = await dispatch.brands.list();
// Response
[
{
"id": "br_abc123",
"name": "Acme Support",
"slug": "acme-support",
"createdAt": "2024-01-01T00:00:00Z"
}
]Get Brand
const brand = await dispatch.brands.get('br_abc123');Create Brand
const brand = await dispatch.brands.create({
name: 'New Brand',
slug: 'new-brand' // Optional, auto-generated if omitted
});Tickets
List Tickets
const { data: tickets, pagination } = await dispatch.tickets.list('br_abc123', {
status: 'open',
limit: 20,
cursor: 'cursor_xyz' // For pagination
});
// Response
{
"data": [
{
"id": "tkt_xyz789",
"ticketNumber": "TKT-1042",
"title": "Need help with my order",
"status": "open",
"priority": "normal",
"customer": {
"id": "cus_def456",
"email": "[email protected]",
"name": "Jane Doe"
},
"customFields": {},
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
],
"pagination": {
"hasMore": true,
"nextCursor": "cursor_abc"
}
}Get Ticket
const ticket = await dispatch.tickets.get('br_abc123', 'tkt_xyz789');Create Ticket
const ticket = await dispatch.tickets.create('br_abc123', {
title: 'Need help with my order',
body: 'My order #12345 has not arrived yet.',
customerEmail: '[email protected]',
customerName: 'Jane Doe', // Optional
priority: 'normal', // 'low' | 'normal' | 'high' | 'urgent'
customFields: {
orderId: '12345',
orderTotal: 99.99
}
});Update Ticket
const ticket = await dispatch.tickets.update('br_abc123', 'tkt_xyz789', {
status: 'pending',
priority: 'high',
assigneeId: 'usr_abc123',
customFields: {
resolution: 'Shipped replacement'
}
});Close Ticket
await dispatch.tickets.close('br_abc123', 'tkt_xyz789', {
resolution: 'Resolved - replacement shipped'
});Comments
List Comments
const { data: comments } = await dispatch.tickets.listComments(
'br_abc123',
'tkt_xyz789'
);
// Response
[
{
"id": "cmt_abc123",
"body": "Thanks for reaching out! Let me look into this.",
"authorType": "STAFF",
"authorName": "Support Team",
"authorId": "usr_xyz789",
"createdAt": "2024-01-15T11:00:00Z"
}
]Add Comment
const comment = await dispatch.tickets.addComment('br_abc123', 'tkt_xyz789', {
body: 'Your order has shipped! Tracking: 1Z999...',
authorType: 'STAFF', // 'STAFF' | 'CUSTOMER' | 'SYSTEM'
authorName: 'Support Team',
authorId: 'usr_xyz789', // Optional
internal: false // Set true for internal notes
});Author Types
| Type | Use For | Sends Email |
|---|---|---|
| STAFF | Agent replies | Yes (to customer) |
| CUSTOMER | Customer replies | Yes (to agents) |
| SYSTEM | Automated messages | No |
Customers
List Customers
const { data: customers } = await dispatch.customers.list('br_abc123', {
search: '[email protected]'
});Get Customer
const customer = await dispatch.customers.get('br_abc123', 'cus_def456');
// Response
{
"id": "cus_def456",
"email": "[email protected]",
"name": "Jane Doe",
"ticketCount": 5,
"createdAt": "2024-01-01T00:00:00Z"
}Get Customer Tickets
const { data: tickets } = await dispatch.customers.listTickets(
'br_abc123',
'cus_def456'
);Attachments
Attachments use presigned URLs. Upload directly to cloud storage without going through our API.
Get Upload URL
const { uploadUrl, attachmentId } = await dispatch.attachments.getUploadUrl(
'br_abc123',
'tkt_xyz789',
{
filename: 'screenshot.png',
contentType: 'image/png',
size: 102400 // bytes
}
);
// Upload file directly to the presigned URL
await fetch(uploadUrl, {
method: 'PUT',
body: fileBuffer,
headers: { 'Content-Type': 'image/png' }
});
// Confirm upload
await dispatch.attachments.confirmUpload('br_abc123', 'tkt_xyz789', attachmentId);Get Download URL
const { downloadUrl } = await dispatch.attachments.getDownloadUrl(
'br_abc123',
'tkt_xyz789',
'att_abc123'
);
// URL is valid for 15 minutesPagination & Filtering
List endpoints support cursor-based pagination and filtering:
const { data, pagination } = await dispatch.tickets.list('br_abc123', {
// Pagination
limit: 20, // 1-100, default 20
cursor: 'cursor_xyz', // From previous response
// Filtering
status: 'open', // 'open' | 'pending' | 'closed'
priority: 'high', // 'low' | 'normal' | 'high' | 'urgent'
assigneeId: 'usr_abc123',
customerId: 'cus_def456',
// Search
search: 'order #12345',
// Date range
createdAfter: '2024-01-01T00:00:00Z',
createdBefore: '2024-01-31T23:59:59Z',
// Sorting
sort: 'createdAt', // Field to sort by
order: 'desc' // 'asc' | 'desc'
});
// Paginate through all results
let allTickets = [];
let cursor = undefined;
do {
const { data, pagination } = await dispatch.tickets.list('br_abc123', {
cursor,
limit: 100
});
allTickets.push(...data);
cursor = pagination.nextCursor;
} while (cursor);Error Handling
The API returns standard HTTP status codes. Error responses include a code and message:
{
"error": {
"code": "TICKET_NOT_FOUND",
"message": "Ticket with ID tkt_xyz789 not found",
"details": {} // Additional context if available
}
}Status Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request - Invalid parameters |
| 401 | Unauthorized - Invalid or missing API key |
| 403 | Forbidden - No access to this resource |
| 404 | Not Found - Resource doesn't exist |
| 429 | Too Many Requests - Rate limited |
| 500 | Internal Server Error |
SDK Error Handling
import { DispatchError } from '@dispatchtickets/sdk';
try {
const ticket = await dispatch.tickets.get('br_abc123', 'tkt_invalid');
} catch (error) {
if (error instanceof DispatchError) {
console.error(error.code); // 'TICKET_NOT_FOUND'
console.error(error.message); // 'Ticket with ID...'
console.error(error.status); // 404
}
}