Core Concepts
Understand the building blocks of Dispatch Tickets. Every resource in the API maps to one of these concepts.
Overview
Dispatch Tickets is organized around a simple hierarchy. An account owns one or more brands. Each brand is an isolated container of tickets, customers, statuses, and configuration. This makes it easy to support multiple products, clients, or business units from a single API integration.
Account
└── Brand (isolated ticket container)
├── Tickets
│ ├── Comments
│ ├── Attachments
│ └── Watchers
├── Customers & Companies
├── Statuses (system + custom)
├── Categories
└── TagsBrands
A brand (also called a workspace) is the top-level container for all ticketing data. Each brand has its own customers, statuses, categories, tags, and email configuration. Data is fully isolated between brands.
Common uses for multiple brands:
- Separate support queues for different products
- Client-specific inboxes for agencies
- Regional support teams (US, EU, APAC)
- Departmental separation (Support, Sales, Billing)
const brand = await dispatch.brands.create({
name: 'Acme Support',
slug: 'acme', // Used in email addresses
});
console.log(brand.id); // 'br_abc123'Every API endpoint that accesses tickets, comments, or other data is scoped to a brand via /brands/:brandId/....
Tickets
Tickets are the core resource. Each ticket has a title, status, priority, and optional fields like assignee, customer, category, tags, and custom fields.
const ticket = await dispatch.tickets.create('br_abc123', {
title: 'Cannot access my account',
priority: 'high',
source: 'email',
customFields: {
requesterEmail: '[email protected]',
requesterName: 'Jane Doe',
orderId: 'ORD-12345',
plan: 'enterprise',
},
});
console.log(ticket.ticketNumber); // 'DT-1042'Ticket Fields
| Field | Type | Description |
|---|---|---|
| title | string | Short summary of the issue |
| status | string | Current status (open, pending, resolved, closed, or custom) |
| priority | string | low, normal, high, or urgent |
| source | string | Channel origin (api, email, web, slack, etc.) |
| customFields | object | Any JSON data (schemaless) |
| customFields.requesterEmail | string | Who the ticket is from. Creates the contact, or links to an existing one (with requesterName) |
| companyId | string | The company the ticket is for. Defaults to the contact's company. See Ticket company |
| company | object | Instead of companyId: { sourceOrgId, name }, your own id for the company, created if new |
Source Tracking
Every ticket tracks where it came from. The source field records the channel (api, email, web, slack, sms, etc.), sourceId stores the original ID from that channel, and sourceData holds channel-specific metadata as JSON.
Comments
Comments are the conversation thread on a ticket. Each comment has a body (text or HTML), an author, and an author type indicating whether it came from a staff member or customer.
const comment = await dispatch.comments.create('br_abc123', 'tkt_def456', {
body: 'Thanks for reaching out! Let me look into this.',
authorType: 'AGENT',
authorName: 'Support Team',
authorEmail: '[email protected]',
});Author Types
AGENT- Team member reply (sent to the customer unless it's an internal note)CUSTOMER- Customer reply (triggers notification to assignee)SYSTEM- Automated message (status changes, assignments)
Comments can also include attachments and support threading via the parentId field for nested replies.
Customers & Companies
Customers (contacts) are created automatically when a ticket includes a customFields.requesterEmail that doesn't already exist. You can also create them explicitly.
// Customer is created automatically from ticket data
await dispatch.tickets.create('br_abc123', {
title: 'Billing question',
customFields: {
requesterEmail: '[email protected]',
requesterName: 'New Customer',
},
});
// A customer record now exists for [email protected]Customers can be grouped into companies for B2B use cases. Companies let you see all tickets from an organization in one place and apply shared settings.
Customer Fields
email- Primary identifier (unique per brand)name- Display namecompany- Optional company associationcustomFields- Any additional metadata as JSON
Ticket company
Each ticket also records the company it is for. Usually that is the contact's own company, and you don't need to set anything. When one person works for several companies, an agency or a consultant for example, set the company on the ticket instead. They stay one contact, and each ticket is filed under the right company.
curl -X POST https://api.dispatchtickets.com/v1/brands/br_abc123/tickets \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"title": "Export is stuck",
"customFields": { "requesterEmail": "[email protected]" },
"company": { "sourceOrgId": "org_42", "name": "Globex" }
}'companyIduses a company that already exists in the brand.company.sourceOrgIdis your own id for the company. The first ticket creates the company (so includename), and later tickets reuse it.- Leave both out to use the contact's company.
PATCHa ticket withcompanyIdto move it, ornullto clear it. - List tickets for one company with
?companyId=cmp_.... Ticket responses includecompanyIdandcompany; webhook payloads includecompanyIdandcompanyName.
Statuses
Every brand starts with four system statuses: open, pending, resolved, and closed. You can add custom statuses to match your workflow.
For a deep dive into status configuration and lifecycle management, see the Statuses & Workflows guide.
Categories & Tags
Categories provide a single, structured classification for each ticket (e.g., "Billing", "Technical", "Feature Request"). A ticket can have one category.
Tags are flexible labels that can be applied to tickets for filtering and organization. A ticket can have multiple tags (e.g., "urgent", "enterprise", "bug").
const ticket = await dispatch.tickets.create('br_abc123', {
title: 'Feature request: Dark mode',
customerEmail: '[email protected]',
categoryId: 'cat_billing',
tagIds: ['tag_feature', 'tag_enterprise'],
});Both categories and tags are managed per brand. Create them in the dashboard under Settings → Brand → Categories / Tags, or via the API.
Attachments
Attachments are files uploaded to tickets or comments. Files are stored in Cloudflare R2 and accessed via presigned URLs. Files never pass through the API server directly.
Upload Flow
- Request an upload URL from the API
- Upload the file directly to the presigned URL
- Confirm the upload to finalize the attachment
// 1. Request upload URL
const upload = await dispatch.attachments.requestUpload('br_abc123', 'tkt_def456', {
filename: 'screenshot.png',
contentType: 'image/png',
});
// 2. Upload directly to presigned URL
await fetch(upload.uploadUrl, {
method: 'PUT',
body: file,
headers: { 'Content-Type': 'image/png' },
});
// 3. Confirm the upload
await dispatch.attachments.confirm('br_abc123', 'tkt_def456', upload.id);Download URLs are also presigned and time-limited. Request a fresh download URL each time rather than caching them.