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

# List events

> Query stored account and keyword monitor events with filters and cursor pagination

<blockquote className="agent-llms-directive">
  For the complete documentation index, see <a href="/llms.txt">llms.txt</a>.
</blockquote>

<Callout icon="circle-check" color="#16a34a">
  **Free** - does not consume credits
</Callout>

<Note>
  Listing stored events is free. Event and webhook deliveries are included in active monitor billing.
</Note>

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://xquik.com/api/v1/events?limit=10&monitorId=7" \
    -H "x-api-key: xq_YOUR_KEY_HERE" \
    | jq '. as $page | .events[] | {
      event_id: .id,
      event_type: .type,
      monitor_type: .monitorType,
      monitor_id: .monitorId,
      keyword_monitor_id: (.keywordMonitorId // null),
      username: (.username // null),
      query: (.query // null),
      occurred_at: .occurredAt,
      tweet_id: (.data.id // null),
      tweet_text: (.data.text // null),
      author_username: (.data.author.userName // null),
      event_detail_endpoint: ("/api/v1/events/" + .id),
      delivery_join_key: .id,
      has_more: $page.hasMore,
      next_cursor: ($page.nextCursor // null)
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    "https://xquik.com/api/v1/events?limit=10&monitorId=7",
    {
      headers: { "x-api-key": "xq_YOUR_KEY_HERE" },
    }
  );
  const data = await response.json();
  const eventRows = data.events.map((event) => ({
    event_id: event.id,
    event_type: event.type,
    monitor_type: event.monitorType,
    monitor_id: event.monitorId,
    keyword_monitor_id: event.keywordMonitorId ?? null,
    username: event.username ?? null,
    query: event.query ?? null,
    occurred_at: event.occurredAt,
    tweet_id: event.data?.id ?? null,
    tweet_text: event.data?.text ?? null,
    author_username: event.data?.author?.userName ?? null,
    event_detail_endpoint: `/api/v1/events/${event.id}`,
    delivery_join_key: event.id,
    has_more: data.hasMore,
    next_cursor: data.nextCursor ?? null,
  }));

  for (const row of eventRows) {
    process.stdout.write(`${JSON.stringify(row)}\n`);
  }
  ```

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

  response = requests.get(
      "https://xquik.com/api/v1/events",
      headers={"x-api-key": "xq_YOUR_KEY_HERE"},
      params={"limit": 10, "monitorId": "7"},
  )
  data = response.json()
  event_rows = []
  for event in data["events"]:
      tweet = event.get("data") if isinstance(event.get("data"), dict) else {}
      author = tweet.get("author") if isinstance(tweet.get("author"), dict) else {}
      event_rows.append(
          {
              "event_id": event["id"],
              "event_type": event["type"],
              "monitor_type": event["monitorType"],
              "monitor_id": event["monitorId"],
              "keyword_monitor_id": event.get("keywordMonitorId"),
              "username": event.get("username"),
              "query": event.get("query"),
              "occurred_at": event["occurredAt"],
              "tweet_id": tweet.get("id"),
              "tweet_text": tweet.get("text"),
              "author_username": author.get("userName"),
              "event_detail_endpoint": f"/api/v1/events/{event['id']}",
              "delivery_join_key": event["id"],
              "has_more": data["hasMore"],
              "next_cursor": data.get("nextCursor"),
          }
      )
  for row in event_rows:
      print(json.dumps(row))
  ```

  ```go Go theme={null}
  package main

  import (
    "encoding/json"
    "log"
    "net/http"
    "os"
  )

  type EventAuthor struct {
    UserName *string `json:"userName"`
  }

  type EventPayload struct {
    ID     *string      `json:"id"`
    Text   *string      `json:"text"`
    Author *EventAuthor `json:"author"`
  }

  type Event struct {
    ID               string       `json:"id"`
    Type             string       `json:"type"`
    MonitorType      string       `json:"monitorType"`
    MonitorID        string       `json:"monitorId"`
    KeywordMonitorID *string      `json:"keywordMonitorId"`
    Username         *string      `json:"username"`
    Query            *string      `json:"query"`
    OccurredAt       string       `json:"occurredAt"`
    Data             EventPayload `json:"data"`
  }

  type EventResponse struct {
    Events     []Event `json:"events"`
    HasMore    bool    `json:"hasMore"`
    NextCursor *string `json:"nextCursor"`
  }

  type EventRow struct {
    DeliveryJoinKey      string  `json:"delivery_join_key"`
    EventDetailEndpoint  string  `json:"event_detail_endpoint"`
    EventID              string  `json:"event_id"`
    EventType            string  `json:"event_type"`
    MonitorType          string  `json:"monitor_type"`
    MonitorID            string  `json:"monitor_id"`
    KeywordMonitorID     *string `json:"keyword_monitor_id"`
    Username             *string `json:"username"`
    Query                *string `json:"query"`
    OccurredAt           string  `json:"occurred_at"`
    TweetID              *string `json:"tweet_id"`
    TweetText            *string `json:"tweet_text"`
    AuthorUsername       *string `json:"author_username"`
    HasMore              bool    `json:"has_more"`
    NextCursor           *string `json:"next_cursor"`
  }

  func main() {
    req, err := http.NewRequest("GET", "https://xquik.com/api/v1/events?limit=10&monitorId=7", nil)
    if err != nil {
      log.Fatal(err)
    }
    req.Header.Set("x-api-key", "xq_YOUR_KEY_HERE")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
      log.Fatal(err)
    }
    defer resp.Body.Close()

    var data EventResponse
    if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
      log.Fatal(err)
    }

    encoder := json.NewEncoder(os.Stdout)
    for _, event := range data.Events {
      var authorUsername *string
      if event.Data.Author != nil {
        authorUsername = event.Data.Author.UserName
      }

      row := EventRow{
        DeliveryJoinKey:     event.ID,
        EventDetailEndpoint: "/api/v1/events/" + event.ID,
        EventID:             event.ID,
        EventType:           event.Type,
        MonitorType:         event.MonitorType,
        MonitorID:           event.MonitorID,
        KeywordMonitorID:    event.KeywordMonitorID,
        Username:            event.Username,
        Query:               event.Query,
        OccurredAt:          event.OccurredAt,
        TweetID:             event.Data.ID,
        TweetText:           event.Data.Text,
        AuthorUsername:      authorUsername,
        HasMore:             data.HasMore,
        NextCursor:          data.NextCursor,
      }
      if err := encoder.Encode(row); err != nil {
        log.Fatal(err)
      }
    }
  }
  ```
</CodeGroup>

These examples emit one stored event row per line. Store `event_id`,
`monitor_type`, `monitor_id`, `event_type`, `occurred_at`, tweet fields from
`data`, `event_detail_endpoint`, `delivery_join_key`, and `next_cursor`; pass
`nextCursor` as `after` until `hasMore` is `false`.

## Source filter examples

Use `monitorId` for account monitor events and `keywordMonitorId` for keyword
monitor events. Do not pass a keyword monitor ID as `monitorId`; that filter
matches account monitor events only.

<CodeGroup>
  ```bash Account monitor events theme={null}
  curl "https://xquik.com/api/v1/events?limit=50&monitorId=7" \
    -H "x-api-key: xq_YOUR_KEY_HERE" \
    | jq '{events: [.events[] | {id, type, monitorId, username}], hasMore, nextCursor}'
  ```

  ```bash Keyword monitor events theme={null}
  curl "https://xquik.com/api/v1/events?limit=50&keywordMonitorId=21" \
    -H "x-api-key: xq_YOUR_KEY_HERE" \
    | jq '{events: [.events[] | {id, type, keywordMonitorId, query}], hasMore, nextCursor}'
  ```
</CodeGroup>

## Query parameters

<ParamField query="limit" type="number">
  Results per page. Default `50`, max `100`.
</ParamField>

<ParamField query="monitorId" type="string">
  Filter account-monitor events by account monitor ID. Use `keywordMonitorId`
  for keyword-monitor events. Omit both filters to return events from all
  monitors.
</ParamField>

<ParamField query="keywordMonitorId" type="string">
  Filter keyword-monitor events by keyword monitor ID. Use the `id` returned by
  keyword monitor endpoints.
</ParamField>

<ParamField query="eventType" type="string">
  Filter by event type. Valid types: `tweet.new`, `tweet.quote`,
  `tweet.reply`, `tweet.retweet`, `tweet.media`, `tweet.link`, `tweet.poll`,
  `tweet.mention`, `tweet.hashtag`, `tweet.longform`,
  `profile.avatar.changed`, `profile.banner.changed`,
  `profile.name.changed`, `profile.username.changed`, `profile.bio.changed`,
  `profile.location.changed`, `profile.url.changed`,
  `profile.verified.changed`, `profile.protected.changed`,
  `profile.pinned_tweet.changed`, `profile.unavailable.changed`. Omit to
  return all types.
</ParamField>

<ParamField query="after" type="string">
  Cursor for pagination. Pass the `nextCursor` value from a previous response to fetch the next page.
</ParamField>

## Headers

<ParamField header="x-api-key" type="string" required>
  Your API key.
</ParamField>

## Response

<Tabs>
  <Tab title="200 OK">
    <ResponseField name="events" type="array">
      List of event objects matching the query.

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

        <ResponseField name="type" type="string">
          Event type from the monitor event type list.
        </ResponseField>

        <ResponseField name="monitorId" type="string">
          Account monitor ID for account events, or keyword monitor ID for keyword events.
        </ResponseField>

        <ResponseField name="monitorType" type="string">
          Source type: `account` or `keyword`.
        </ResponseField>

        <ResponseField name="keywordMonitorId" type="string">
          Keyword monitor ID. Present only for keyword monitor events.
        </ResponseField>

        <ResponseField name="username" type="string">
          X username that triggered the event. Present only for account monitor events.
        </ResponseField>

        <ResponseField name="query" type="string">
          Keyword query that matched the event. Present only for keyword monitor events.
        </ResponseField>

        <ResponseField name="occurredAt" type="string">
          ISO 8601 timestamp of when the event occurred on X.
        </ResponseField>

        <ResponseField name="data" type="object">
          Raw tweet object from the X real-time stream. Fields vary by event type.

          <Info>
            The `data` field contains the raw tweet object. See [Webhooks Overview](/webhooks/overview) for the full data schema of each event type.
          </Info>
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="hasMore" type="boolean">
      `true` if additional pages exist beyond this result set.
    </ResponseField>

    <ResponseField name="nextCursor" type="string">
      Pagination cursor. Pass as the `after` query parameter to fetch the next page. Present only when `hasMore` is `true`.
    </ResponseField>

    ```json theme={null}
    {
      "events": [
        {
          "id": "9001",
          "type": "tweet.new",
          "monitorId": "7",
          "monitorType": "account",
          "username": "elonmusk",
          "occurredAt": "2026-02-24T14:22:00.000Z",
          "data": {
            "id": "1893456789012345678",
            "text": "The future is now.",
            "author": {
              "id": "44196397",
              "userName": "elonmusk",
              "name": "Elon Musk"
            },
            "isRetweet": false,
            "isReply": false,
            "isQuote": false,
            "createdAt": "2026-02-24T14:22:00.000Z"
          }
        },
        {
          "id": "9002",
          "type": "tweet.reply",
          "monitorId": "7",
          "monitorType": "account",
          "username": "elonmusk",
          "occurredAt": "2026-02-24T15:05:30.000Z",
          "data": {
            "id": "1893456789012345999",
            "text": "Absolutely. Shipping next week.",
            "author": {
              "id": "44196397",
              "userName": "elonmusk",
              "name": "Elon Musk"
            },
            "isRetweet": false,
            "isReply": true,
            "isQuote": false,
            "inReplyToId": "1893456789012345900",
            "createdAt": "2026-02-24T15:05:30.000Z"
          }
        }
      ],
      "hasMore": true,
      "nextCursor": "MjAyNi0wMi0yNFQxNTowNTozMC4wMDBafDkwMDI="
    }
    ```
  </Tab>

  <Tab title="401 Unauthenticated">
    ```json theme={null}
    { "error": "unauthenticated" }
    ```

    Missing or invalid API key. Check the `x-api-key` header value.
  </Tab>

  <Tab title="429 Rate Limited">
    ```json theme={null}
    { "error": "rate_limit_exceeded", "message": "Too many requests. Try again later.", "retryAfter": 1 }
    ```

    Too many requests. Wait for the `Retry-After` header before retrying.
  </Tab>
</Tabs>

## Event inventory handoff

Use this endpoint when an agent, dashboard, or support workflow needs a compact
inventory of stored monitor events before looking up details or webhook
delivery status.

<CardGroup cols={2}>
  <Card title="Event Row" icon="fingerprint">
    Store `event_id`, `event_type`, `occurred_at`, and
    `event_detail_endpoint`. Use [Get Event](/api-reference/events/get) when a
    later step needs the full event payload.
  </Card>

  <Card title="Monitor Source" icon="radar">
    Store `monitor_type`, `monitor_id`, `keyword_monitor_id`, `username`, and
    `query` so account and keyword monitor events stay separate in exports.
    Use `monitorId` for account-monitor filters and `keywordMonitorId` for
    keyword-monitor filters.
  </Card>

  <Card title="Tweet Fields" icon="message-circle">
    Store `tweet_id`, `tweet_text`, and `author_username` from `data` when the
    event contains tweet content. Other event types can leave those fields
    empty.
  </Card>

  <Card title="Delivery Join" icon="link">
    Store `delivery_join_key` as the event ID. In webhook delivery rows, match
    it to `streamEventId` from
    [List Deliveries](/api-reference/webhooks/deliveries).
  </Card>

  <Card title="Delivery Status" icon="activity">
    After joining deliveries, store `status`, `attempts`, `lastStatusCode`,
    `lastError`, `createdAt`, and `deliveredAt` with the event row.
  </Card>

  <Card title="Cursor Checkpoint" icon="milestone">
    Store `next_cursor` only when `has_more` is `true`. Pass it as `after` for
    the next page and stop when `has_more` is `false`.
  </Card>
</CardGroup>

## Pagination

Events use **cursor-based pagination**. Each response includes `hasMore` and (when `true`) a `nextCursor` value. Pass `nextCursor` as the `after` query parameter to retrieve the next page.

<CodeGroup>
  ```bash First page theme={null}
  curl "https://xquik.com/api/v1/events?limit=10&monitorId=7" \
    -H "x-api-key: xq_YOUR_KEY_HERE" \
    | jq '{event_ids: [.events[].id], has_more: .hasMore, next_cursor: (.nextCursor // null)}'
  ```

  ```bash Next page theme={null}
  curl "https://xquik.com/api/v1/events?limit=10&monitorId=7&after=MjAyNi0wMi0yNFQxNTowNTozMC4wMDBafDkwMDI=" \
    -H "x-api-key: xq_YOUR_KEY_HERE" \
    | jq '{event_ids: [.events[].id], has_more: .hasMore, next_cursor: (.nextCursor // null)}'
  ```
</CodeGroup>

Continue fetching pages until `hasMore` is `false`. Cursors are opaque strings. Do not parse or construct them manually.

<Note>
  **Related:** [Get Event](/api-reference/events/get) · [List Deliveries](/api-reference/webhooks/deliveries) · [List Monitors](/api-reference/monitors/list)
</Note>
