Cross-ticket responses query API

This article and the rest of the API documentation in this section are written for a technical audience — integrators and developers connecting external systems to Tickiti. Familiarity with HTTP, REST, JSON and bearer-token authentication is assumed.

This endpoint returns responses drawn from across every ticket the token can see — the bulk counterpart to List responses, which works one ticket at a time. Filter by a created-at window, queue, the internal/staff flags or a set of ticket numbers, and page through the matches with a keyset cursor. Use it to pull all activity in a date range without walking tickets one by one: to feed an external analytics store, build a digest, or reconcile an audit log.

Endpoint

POST /api/v1/tickets/responses/query

Headers

  1. Authorization: Bearer YOUR_TOKEN — required. Token must have the tickets:read ability. A tickets:write token also works (write implies read).
  2. Content-Type: application/json — required. No Idempotency-Key is needed (this is a read).

Body

Every field is optional. With an empty body the endpoint returns the first page of responses across all tickets you can see, oldest first.

  1. created_from — lower bound on a response’s creation time. A bare YYYY-MM-DD means that day from 00:00:00; a full YYYY-MM-DD HH:MM:SS is used as given. Compared against created_at as stored (UTC wall-clock).
  2. created_to — upper bound. A bare YYYY-MM-DD means that day to 23:59:59.
  3. is_internaltrue for internal staff notes only, false for public responses only. Omit to include both.
  4. staff_responsetrue for staff-authored responses only, false for customer responses only. Omit for either.
  5. queue — array of queue names; restrict to responses on tickets in those queues.
  6. queue_id — array of queue ids; the same restriction by id.
  7. ticket_number — array of 6-digit ticket numbers; restrict to responses on those tickets.
  8. include_deletedtrue to include soft-deleted responses. Defaults to false.
  9. fields — array selecting which heavy fields to return, any of html, plain_text, attachments. Omit a field to skip it server-side; the default returns all three. The metadata fields are always returned. For a cheap metadata-only pull, pass "fields": [].
  10. limit — page size. Defaults to 200, maximum 1000.
  11. cursor — a { "created_at": ..., "id": ... } object copied verbatim from a previous page’s next_cursor. Omit it for the first page. See Paging below.

What you can see

Results obey the same visibility rule as the staff interface: a response is included only if its ticket sits in a queue the acting user can view, or that user is a participant on the ticket. A non-staff token owner is refused with 403. The queue, queue_id and ticket_number filters narrow within that visible set — they can never widen it, so a token cannot read responses its owner could not read on screen.

Example

Pull the metadata for every response created in the Sales queue over a two-day window, dropping the bodies:

curl -X POST https://support.sole-provider.example/api/v1/tickets/responses/query \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "created_from": "2026-05-01",
    "created_to": "2026-05-02",
    "queue": ["Sales"],
    "fields": ["attachments"],
    "limit": 200
  }'

Successful response

A responses array, ordered oldest to newest by created_at (ties broken by id), plus the next_cursor to fetch the following page and the count on this page. Each row carries the ticket it belongs to, so you can group responses back onto their tickets.

{
  "ok": true,
  "data": {
    "responses": [
      {
        "response_id": "41",
        "ticket_number": "220009",
        "is_internal": true,
        "is_pinned": false,
        "staff_response": true,
        "created_by_email": "priya.sharma@sole-provider.example",
        "created_at": "2026-05-02T10:05:11+00:00",
        "updated_at": "2026-05-02T10:05:11+00:00",
        "is_email_sourced": false,
        "html": "<p>Internal note: replacement authorised.</p>",
        "plain_text": "Internal note: replacement authorised.",
        "attachments": []
      }
    ],
    "next_cursor": { "created_at": "2026-05-02 10:05:11", "id": 41 },
    "count": 1
  }
}

When fields omits a heavy field, that key is absent from each row rather than null. The metadata keys above are always present.

Paging

Paging is keyset-based, not offset-based, so it stays correct even as new responses arrive mid-walk. To read the whole result set: call the endpoint, process data.responses, then if data.next_cursor is not null call again with that object as cursor. Stop when next_cursor comes back null. Keep the other filters identical across pages.

Error responses

  1. 422 validation_failed — a field has the wrong type, fields contains a value other than html/plain_text/attachments, limit is out of range, or a supplied cursor is missing its created_at or id.
  2. 401 unauthenticated — bad or missing token.
  3. 403 forbidden — token lacks the tickets:read (or tickets:write) ability, or the token owner is not a staff account.

Where to go next

  1. List responses API — the single-ticket version, when you already know the ticket.
  2. Download attachment API — fetch the bytes of an attachment listed on a response.
  3. MCP server — reach this endpoint from an AI assistant via the query_responses and export_responses tools.