> ## Documentation Index
> Fetch the complete documentation index at: https://docs.typewise.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Tickets API

> Retrieve tickets programmatically with filtering, sorting, and pagination.

Fetch tickets from your Typewise workspace using a REST API. Use this to build
reporting pipelines, trigger workflows in Zapier, or sync ticket data with
external systems.

<Info>
  You need an **API key**. Create one in [Workspace Settings → API
  Keys](/documentation/settings/api-keys).
</Info>

***

## Endpoint

```
GET https://platform-api.typewise.app/external/v1/tickets
```

**Authentication:** Pass your API key as a Bearer token.

```
Authorization: Bearer YOUR_API_KEY
```

***

## Quick start

```bash cURL theme={null}
curl -G 'https://platform-api.typewise.app/external/v1/tickets' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  --data-urlencode 'limit=5'
```

***

## Query parameters

<ParamField query="pageIndex" type="number" default="0">
  Zero-based page index for pagination.
</ParamField>

<ParamField query="limit" type="number" default="20">
  Number of tickets to return per page.
</ParamField>

<ParamField query="sortBy" type="string" default="updatedAt">
  Field to sort by. Supported values include `createdAt`, `updatedAt`, and
  `humanAgentRating`.
</ParamField>

<ParamField query="sortDirection" type="string" default="desc">
  Sort order. Use `asc` for ascending or `desc` for descending.
</ParamField>

<ParamField query="searchTerm" type="string">
  Free-text search across ticket title, summary, and customer name.
</ParamField>

<ParamField query="filters" type="string">
  URL-encoded JSON object that combines one or more filter rules. See [Filters](#filters) below.
</ParamField>

***

## Filters

Pass a JSON object in the `filters` query parameter to narrow results. The
object has a `combinator` (always `"and"`) and an array of `rules`.

Each rule specifies a `field`, an `operator`, and a `value`.

### Filter fields

| Field              | Operator                     | Value                                                                                | Example                                            |
| :----------------- | :--------------------------- | :----------------------------------------------------------------------------------- | :------------------------------------------------- |
| `status`           | `in`                         | Array of statuses: `open`, `needs_human_attention`, `waiting_for_customer`, `closed` | `["open", "needs_human_attention"]`                |
| `csatRating`       | `in`                         | Array of integers (1–5)                                                              | `[4, 5]`                                           |
| `humanAgentRating` | `in`                         | Array of integers (1–5) and/or `"no_assignee"` for unrated                           | `[5]`, `["no_assignee"]`, `["no_assignee", 1, 2]`  |
| `assigneeId`       | `in`                         | Array of user UUIDs                                                                  | `["uuid-of-agent"]`                                |
| `teamId`           | `in`                         | Array of team UUIDs                                                                  | `["uuid-of-team"]`                                 |
| `priority`         | `=`                          | Integer (1 = highest)                                                                | `1`                                                |
| `channelType`      | `in`                         | Array of channel types: `email`, `chat_widget`                                       | `["email"]`                                        |
| `createdAt`        | `between`, `after`, `before` | ISO 8601 datetime string(s). `between` takes a two-element array.                    | `["2026-01-01T00:00:00Z", "2026-03-31T23:59:59Z"]` |
| `updatedAt`        | `between`, `after`, `before` | ISO 8601 datetime string(s). `between` takes a two-element array.                    | `"2026-03-01T00:00:00Z"`                           |

<Note>
  For `humanAgentRating`, use the sentinel value `"no_assignee"` to match
  conversations that have not been rated by an agent or admin yet. You can
  combine it with numeric scores in the same `in` rule.
</Note>

### Example filter

This filter returns open tickets with a CSAT rating of 4 or 5, created in Q1
2026:

```json theme={null}
{
  "combinator": "and",
  "rules": [
    { "field": "status", "operator": "in", "value": ["open"] },
    { "field": "csatRating", "operator": "in", "value": [4, 5] },
    {
      "field": "createdAt",
      "operator": "between",
      "value": ["2026-01-01T00:00:00Z", "2026-03-31T23:59:59Z"]
    }
  ]
}
```

***

## Response

<ResponseField name="totalCount" type="number" required>
  Total number of tickets matching the query across all pages.
</ResponseField>

<ResponseField name="tickets" type="Ticket[]" required>
  Array of ticket objects for the current page.

  <Expandable title="Ticket object">
    <ResponseField name="id" type="string" required>
      Unique ticket identifier.
    </ResponseField>

    <ResponseField name="status" type="string" required>
      Current ticket status. One of: `open`, `needs_human_attention`,
      `waiting_for_customer`, `closed`.
    </ResponseField>

    <ResponseField name="resolutionStatus" type="string" required>
      Resolution status. One of: `unresolved`, `resolved`, `auto_resolved`.
    </ResponseField>

    <ResponseField name="channelType" type="string" required>
      Channel the ticket originated from (e.g. `email`, `chat_widget`).
    </ResponseField>

    <ResponseField name="title" type="string" required>
      Ticket title or subject line.
    </ResponseField>

    <ResponseField name="summary" type="string" required>
      AI-generated summary of the conversation.
    </ResponseField>

    <ResponseField name="priority" type="number" required>
      Priority level (1 = highest).
    </ResponseField>

    <ResponseField name="sentiment" type="number" required>
      Sentiment score of the conversation.
    </ResponseField>

    <ResponseField name="language" type="string">
      Detected language of the conversation.
    </ResponseField>

    <ResponseField name="assigneeId" type="string">
      UUID of the assigned agent, or `null` if unassigned.
    </ResponseField>

    <ResponseField name="teamId" type="string">
      UUID of the assigned team, or `null` if unassigned.
    </ResponseField>

    <ResponseField name="endCustomerId" type="string">
      Identifier of the end customer.
    </ResponseField>

    <ResponseField name="endCustomerName" type="string" required>
      Display name of the end customer.
    </ResponseField>

    <ResponseField name="endCustomerEmail" type="string">
      Email address of the end customer.
    </ResponseField>

    <ResponseField name="endCustomerWaitingForReplySince" type="string">
      ISO 8601 timestamp of when the customer started waiting for a reply.
    </ResponseField>

    <ResponseField name="csatRating" type="number">
      CSAT score (1–5), or `null` if no rating was submitted.
    </ResponseField>

    <ResponseField name="csatComment" type="string">
      Free-text feedback from the CSAT survey, or `null`.
    </ResponseField>

    <ResponseField name="humanAgentRating" type="number">
      Agent/admin conversation rating (1–5), or `null` if unrated. Independent
      of customer CSAT.
    </ResponseField>

    <ResponseField name="humanAgentRatingComment" type="string">
      Optional comment attached to the agent/admin rating, or `null`.
    </ResponseField>

    <ResponseField name="variables" type="object">
      Key-value map of custom variables attached to the ticket.
    </ResponseField>

    <ResponseField name="createdAt" type="string" required>
      ISO 8601 timestamp when the ticket was created.
    </ResponseField>

    <ResponseField name="updatedAt" type="string" required>
      ISO 8601 timestamp of the last ticket update.
    </ResponseField>
  </Expandable>
</ResponseField>

***

## Code examples

<RequestExample>
  ```bash cURL theme={null}
  curl -G 'https://platform-api.typewise.app/external/v1/tickets' \
    -H 'Authorization: Bearer YOUR_API_KEY' \
    --data-urlencode 'pageIndex=0' \
    --data-urlencode 'limit=20' \
    --data-urlencode 'sortBy=updatedAt' \
    --data-urlencode 'sortDirection=desc' \
    --data-urlencode 'filters={
      "combinator": "and",
      "rules": [
        { "field": "status", "operator": "in", "value": ["open", "needs_human_attention"] },
        { "field": "csatRating", "operator": "in", "value": [4, 5] },
        { "field": "createdAt", "operator": "between", "value": ["2026-01-01T00:00:00Z", "2026-03-31T23:59:59Z"] }
      ]
    }'
  ```

  ```javascript Node.js theme={null}
  const params = new URLSearchParams({
    pageIndex: "0",
    limit: "20",
    sortBy: "updatedAt",
    sortDirection: "desc",
    filters: JSON.stringify({
      combinator: "and",
      rules: [
        { field: "status", operator: "in", value: ["open", "needs_human_attention"] },
        { field: "csatRating", operator: "in", value: [4, 5] },
        {
          field: "createdAt",
          operator: "between",
          value: ["2026-01-01T00:00:00Z", "2026-03-31T23:59:59Z"],
        },
      ],
    }),
  });

  const response = await fetch(
    `https://platform-api.typewise.app/external/v1/tickets?${params}`,
    { headers: { Authorization: "Bearer YOUR_API_KEY" } }
  );

  const data = await response.json();
  ```

  ```python Python theme={null}
  import json
  import requests

  filters = {
      "combinator": "and",
      "rules": [
          {"field": "status", "operator": "in", "value": ["open", "needs_human_attention"]},
          {"field": "csatRating", "operator": "in", "value": [4, 5]},
          {
              "field": "createdAt",
              "operator": "between",
              "value": ["2026-01-01T00:00:00Z", "2026-03-31T23:59:59Z"],
          },
      ],
  }

  response = requests.get(
      "https://platform-api.typewise.app/external/v1/tickets",
      headers={"Authorization": "Bearer YOUR_API_KEY"},
      params={
          "pageIndex": 0,
          "limit": 20,
          "sortBy": "updatedAt",
          "sortDirection": "desc",
          "filters": json.dumps(filters),
      },
  )

  data = response.json()
  ```
</RequestExample>

<ResponseExample>
  ```json Response theme={null}
  {
    "totalCount": 42,
    "tickets": [
      {
        "id": "b3e1f9a0-1234-4abc-9def-567890abcdef",
        "status": "open",
        "resolutionStatus": "unresolved",
        "channelType": "email",
        "title": "Order not received",
        "summary": "Customer reports their order #12345 has not arrived after 10 days.",
        "priority": 2,
        "sentiment": -0.6,
        "language": "en",
        "assigneeId": "a1b2c3d4-5678-9abc-def0-1234567890ab",
        "teamId": "f0e1d2c3-b4a5-6789-0abc-def123456789",
        "endCustomerId": "cust-001",
        "endCustomerName": "Jane Doe",
        "endCustomerEmail": "jane.doe@example.com",
        "endCustomerWaitingForReplySince": "2026-03-15T14:30:00Z",
        "csatRating": 4,
        "csatComment": "Quick response, still waiting for resolution.",
        "humanAgentRating": 5,
        "humanAgentRatingComment": "Clear handoff notes and correct resolution.",
        "variables": {
          "orderId": "ORD-12345",
          "accountType": "premium"
        },
        "createdAt": "2026-03-14T09:00:00Z",
        "updatedAt": "2026-03-15T14:30:00Z"
      }
    ]
  }
  ```
</ResponseExample>

***

## Errors

| Status | Body                                        | Cause                                     |
| :----- | :------------------------------------------ | :---------------------------------------- |
| 401    | `{ "error": "Missing or invalid API key" }` | API key is missing, malformed, or revoked |
| 403    | `{ "error": "Insufficient permissions" }`   | Key lacks the `tickets:read` permission   |
| 429    | Rate limit response                         | Too many requests, back off and retry     |

***

## Rate limiting

The API allows **500 requests per 60-second window** per API key. If you exceed
this limit, the API returns `429`. Implement exponential backoff or space your
polling intervals accordingly.

***

## See also

* [API Keys](/documentation/settings/api-keys): Create and manage API keys
* [Conversation rating](/documentation/tickets/conversation-rating): Agent/admin
  ratings in the platform UI
* [All tickets](/documentation/tickets/all-tickets): Browse tickets in the
  platform UI
* [Create tickets via Form API](/guides/channels/form-api): Push tickets into
  Typewise from external systems
