Home Pricing Docs Blog Changelog About Log in Go to your tickets
Browse docs

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.