> ## 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 webhook events & replay monitor activity

> Query stored tweet and profile change events from account and keyword monitors. Filter by monitor or event type and paginate with a cursor. Listing is free.

<Panel>
  <Tabs defaultTabIndex={0} sync={false}>
    <Tab title="200" id="response-events-list-200">
      ```json theme={null}
      {
        "events": [],
        "hasMore": false
      }
      ```
    </Tab>

    <Tab title="400" id="response-events-list-400">
      ```json theme={null}
      {
        "error": "invalid_input",
        "message": "Invalid input. Check the request body."
      }
      ```
    </Tab>

    <Tab title="401" id="response-events-list-401">
      ```json theme={null}
      {
        "error": "unauthenticated",
        "message": "Authentication required. Provide a valid API key or bearer token."
      }
      ```
    </Tab>

    <Tab title="429" id="response-events-list-429">
      ```json theme={null}
      {
        "error": "rate_limit_exceeded",
        "message": "Too many requests. Try again later.",
        "retryAfter": 60
      }
      ```
    </Tab>
  </Tabs>
</Panel>

<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.** This endpoint does not consume credits.
</Callout>

Account events include the current `username` and permanent `xUserId`.
Use `xUserId` to match an account after its username changes.
`monitorName` is your monitor label when Xquik created the event.
A rename leaves past events unchanged.
MCP returns `x_user_id`, `monitor_name`, and prefixed `monitor_id` values.

<Note>
  Active monitor billing includes event and webhook deliveries.
</Note>

Stored monitor events remain available for 90 days. Export every page before
older records expire. The dashboard has no CSV export for monitor events.

<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,
      monitor_name: (.monitorName // null),
      x_user_id: (.xUserId // null),
      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 // .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,
    monitor_name: event.monitorName ?? null,
    x_user_id: event.xUserId ?? null,
    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 ?? 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"],
      "monitor_name": event.get("monitorName"),
      "x_user_id": event.get("xUserId"),
              "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") or 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"`
    MonitorName      *string      `json:"monitorName"`
    XUserID          *string      `json:"xUserId"`
    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"`
    MonitorName          *string `json:"monitor_name"`
    XUserID              *string `json:"x_user_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,
      MonitorName:         event.MonitorName,
      XUserID:             event.XUserID,
        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 `cursor` 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="cursor" type="string">
  Cursor for pagination. Pass the `nextCursor` value from a previous response to fetch the next page. An edited or unknown cursor returns `400`.
</ParamField>

## Headers

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

## Response

### 200 OK

<ResponseField name="events" type="array">
  List of event objects matching the query.
  **Event object fields.**

  <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">
    Post or profile-change data. Post fields match REST responses.

    <Info>
      Post profiles use `username` and `verified`. Legacy fields remain available. See [Webhooks Overview](/webhooks/overview) for each event type.
    </Info>
  </ResponseField>
</ResponseField>

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

<ResponseField name="nextCursor" type="string">
  Pagination cursor. Pass it as `cursor` for the next page.
</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="
}
```

### 400 Invalid cursor

```json theme={null}
{
  "error": "invalid_input",
  "message": "Cursor invalid. Use nextCursor from the previous page, or omit it to start over."
}
```

The `cursor` value is not a `nextCursor` from this route. Send the last
`nextCursor` you received, or omit `cursor` to start from the first page.

### 401 Unauthenticated

```json theme={null}
{ "error": "unauthenticated" }
```

Missing or invalid API key. Check the `x-api-key` header value.

### 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.

## Event inventory handoff

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

| Event inventory column | Response source | Pagination rule |
| - | - | - |
| Event ID | `events[].id` | Open one event or join a webhook delivery. |
| Event type | `events[].type` | Route tweet, reply, quote, or repost records. |
| Monitor source | `events[].monitorType` | Separate account and keyword monitor rows. |
| Account source | `events[].monitorId` and `username` | Filter one tracked X account. |
| Keyword source | `events[].keywordMonitorId` and `query` | Filter one tracked X search. |
| Tweet fields | `events[].data` | Export tweet text, author, and timestamps. |
| More pages | `hasMore` | Continue only while this value is `true`. |
| Next page | `nextCursor` | Pass this value as the next `cursor` parameter. |

<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 `cursor` for
    the next page and stop when `has_more` is `false`.
  </Card>
</CardGroup>

## Pagination

Events use cursor pagination. Pass `nextCursor` as the next `cursor` value.

<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&cursor=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 build them.
An edited or unknown cursor returns `400 invalid_input`. An empty `cursor` returns the first page.

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.