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_ids] string Comma separated channel IDs
filter[tag_ids] string Comma separated tag IDs
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

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 and activity log.

GET /api/tickets/{id}

Example Request

curl https://there-there.app/api/tickets/42 \
  -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": [
            {
                "type": "status_changed",
                "user": { "id": 5, "name": "Jane Smith" },
                "created_at": "2025-06-02T14:15:00+00:00"
            }
        ]
    }
}

Update Ticket Status

PUT /api/tickets/{id}/status
{ "status": "closed" }

Valid status values: open, waiting, closed, spam.

Update Ticket Assignee

PUT /api/tickets/{id}/assignee
{ "assignee_id": 5 }

Pass null for assignee_id to unassign.

Update Ticket Team

PUT /api/tickets/{id}/team
{ "team_id": 3 }

Pass null for team_id to unassign.

Delete Ticket

Tickets are not deleted via the API. Use the Spam status instead.