Tickets
Create Ticket
Create a new inbound ticket on a specific channel. This is useful for integrating external forms or support widgets with There There.
POST /api/tickets
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
channel |
string | yes | The ULID of the channel |
subject |
string | yes | Ticket subject (max 255 characters) |
from_contact.email |
string | yes | Contact email address |
from_contact.name |
string | no | Contact name |
message.body |
string | yes | HTML body of the initial message |
Example Request
curl -X POST "https://there-there.app/api/tickets" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"channel": "01HX9F3K2M...",
"subject": "Cannot connect to the API",
"from_contact": {
"email": "john@customer.com",
"name": "John Doe"
},
"message": {
"body": "<p>I keep getting a 403 error when trying to connect.</p>"
}
}'
Example Response
{
"data": {
"id": 42,
"ulid": "01HX9F3K2M...",
"subject": "Cannot connect to the API",
"status": "open",
"channel": { "id": 1, "name": "Support", "type": "email", "color": "#3b82f6" },
"assignee": null,
"contact": {
"id": 12,
"ulid": "01HX9F3K2N...",
"name": "John Doe",
"email": "john@customer.com",
"avatar_url": null
},
"tags": [],
"created_at": "2025-06-01T10:30:00+00:00",
"updated_at": "2025-06-01T10:30:00+00:00"
}
}
The ticket will be processed through the standard inbound pipeline, which means workflows, AI title generation, notifications, and other automations will run as if the ticket arrived via email.
If the contact email does not exist yet, a new contact will be created automatically.
List Tickets
Retrieve a paginated list of tickets in the current workspace.
GET /api/tickets
Query Parameters
| Parameter | Type | Description |
|---|---|---|
per_page |
integer | Results per page (default: 25, max: 100) |
sort |
string | Sort field: created_at, updated_at, last_activity_at. Prefix with - for descending (default: -updated_at) |
filter[status] |
string | Comma separated statuses: open, waiting, closed, spam |
filter[channel_ulids] |
string | Comma separated channel ULIDs |
filter[tag_ulids] |
string | Comma separated tag ULIDs |
filter[search] |
string | Search term for subject and message content |
filter[assigned_to_me] |
boolean | Only tickets assigned to the authenticated user |
filter[unassigned] |
boolean | Only unassigned tickets |
filter[assigned_user_ulid] |
string | Only tickets assigned to the user with this ULID |
Example Request
curl "https://there-there.app/api/tickets?filter[status]=open&sort=-updated_at&per_page=10" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"
Example Response
{
"data": [
{
"id": 42,
"ulid": "01HX9F3K2M...",
"subject": "Cannot reset my password",
"status": "open",
"channel": { "id": 1, "name": "Support", "type": "email", "color": "#3b82f6" },
"assignee": {
"id": 5,
"name": "Jane Smith",
"email": "jane@example.com",
"avatar_url": null,
"timezone": "UTC"
},
"contact": {
"id": 12,
"ulid": "01HX9F3K2N...",
"name": "John Doe",
"email": "john@customer.com",
"avatar_url": null
},
"tags": [{ "id": 3, "ulid": "01HX9F3K2P...", "name": "urgent", "color": "red" }],
"latest_message_preview": "I tried clicking the link but...",
"summary": null,
"created_at": "2025-06-01T10:30:00+00:00",
"updated_at": "2025-06-02T14:15:00+00:00"
}
],
"links": { "first": "...", "last": "...", "prev": null, "next": "..." },
"meta": { "current_page": 1, "last_page": 5, "per_page": 10, "total": 48 }
}
Get Ticket
Retrieve a single ticket with its messages.
GET /api/tickets/{ticket_ulid}
Example Request
curl https://there-there.app/api/tickets/01HX9F3K2M... \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"
Example Response
{
"data": {
"id": 42,
"ulid": "01HX9F3K2M...",
"subject": "Cannot reset my password",
"status": "open",
"channel": { "id": 1, "name": "Support", "type": "email", "color": "#3b82f6" },
"assignee": null,
"contact": {
"id": 12,
"ulid": "01HX9F3K2N...",
"name": "John Doe",
"email": "john@customer.com",
"avatar_url": null
},
"tags": [],
"latest_message_preview": "I tried clicking the link but...",
"summary": null,
"created_at": "2025-06-01T10:30:00+00:00",
"updated_at": "2025-06-02T14:15:00+00:00",
"messages": [
{
"id": 101,
"ulid": "01HX9F3K2Q...",
"type": "inbound",
"body_html": "<p>I tried clicking the link but it expired.</p>",
"sender": {
"id": 12,
"type": "contact",
"name": "John Doe",
"email": "john@customer.com",
"avatar_url": null
},
"attachments": [],
"is_forward": false,
"created_at": "2025-06-01T10:30:00+00:00"
}
]
}
}
Activities for a ticket are exposed through a separate endpoint. See List Ticket Activities below.
Update Ticket Status
PUT /api/tickets/{ticket_ulid}/status
{ "status": "closed" }
Valid status values: open, waiting, closed, spam.
Update Ticket Assignee
PUT /api/tickets/{ticket_ulid}/assignee
{ "assignee_ulid": "01hx9f3k2a..." }
Pass null for assignee_ulid to unassign.
Update Ticket Team
PUT /api/tickets/{ticket_ulid}/team
{ "team_ulid": "01hx9g4m3r..." }
Pass null for team_ulid to unassign.
List Ticket Activities
Retrieve the activity log for a single ticket (status changes, assignment changes, tag changes, etc.).
GET /api/tickets/{ticket_ulid}/activities
Delete Ticket
Tickets are not deleted via the API. Use the Spam status instead.