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

# Export Twitter bookmarks with Twitter bookmarks API

> Export Twitter bookmarks with tweet text, authors, replies, likes, reposts, views, media, folders, CSV rows & cursor checkpoints from one connected account.

<Panel>
  <Tabs defaultTabIndex={0} sync={false}>
    <Tab title="200" id="response-x-bookmarks-200">
      ```json theme={null}
      {
        "tweets": [],
        "has_next_page": false,
        "next_cursor": ""
      }
      ```
    </Tab>

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

    <Tab title="402" id="response-x-bookmarks-402">
      ```json theme={null}
      {
        "error": "insufficient_credits",
        "message": "Insufficient credits. Top up or subscribe to continue."
      }
      ```
    </Tab>

    <Tab title="424" id="response-x-bookmarks-424">
      ```json theme={null}
      {
        "error": "x_api_unavailable",
        "message": "X data source temporarily unavailable. Try again later."
      }
      ```
    </Tab>

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

    <Tab title="502" id="response-x-bookmarks-502">
      ```json theme={null}
      {
        "error": "x_api_unavailable",
        "message": "X data source temporarily unavailable. Try again later."
      }
      ```
    </Tab>

    <Tab title="503" id="response-x-bookmarks-503">
      ```json theme={null}
      {
        "error": "x_api_unavailable",
        "message": "Xquik is busy right now. Retry shortly."
      }
      ```
    </Tab>
  </Tabs>
</Panel>

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

<Info>
  Export Twitter bookmarks from one connected account. The route
  returns saved tweets, authors, likes, replies, reposts, views, media, and
  cursor checkpoints. [The Twitter developer reference defines bookmarks as
  private, authenticated-user content.](https://docs.x.com/x-api/posts/bookmarks/introduction)
</Info>

Requested result counts are upper bounds for paid authenticated calls. When remaining credits cannot cover the full page or ID list, Xquik returns fewer results. If zero paid results are affordable, it returns `402 insufficient_credits`.

<Callout icon="coins" color="#5c3327">
  **1 credit per result returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit
</Callout>

The route reads bookmarks through your connected X account. Without one, it
returns `424 account_required`. Use [Connect X account](/api-reference/x-accounts/connect)
to add one. When your connected X accounts are busy, it returns `503`. Retry
after the `Retry-After` delay.

Folder reads need X Premium. For any other account, `folderId` returns
`424 bookmark_folders_unavailable`. Omit `folderId` to read all bookmarks.

<CodeGroup>
  ```bash cURL theme={null}
  curl -G https://xquik.com/api/v1/x/bookmarks \
    -H "x-api-key: xq_YOUR_KEY_HERE" | jq

  # Page 2
  curl -G https://xquik.com/api/v1/x/bookmarks \
    --data-urlencode "cursor=abc123" \
    -H "x-api-key: xq_YOUR_KEY_HERE" | jq
  ```

  ```javascript Node.js theme={null}
  const bookmarkFolderId = ""; // Set to a folder ID to export one folder.
  const baseUrl = "https://xquik.com/api/v1/x/bookmarks";
  let pageCursor = "";

  for (let pageIndex = 0; pageIndex < 3; pageIndex += 1) {
    const params = new URLSearchParams();
    if (bookmarkFolderId !== "") params.set("folderId", bookmarkFolderId);
    if (pageCursor !== "") params.set("cursor", pageCursor);

    const query = params.toString();
    const response = await fetch(query === "" ? baseUrl : `${baseUrl}?${query}`, {
      headers: { "x-api-key": "xq_YOUR_KEY_HERE" },
    });
    const page = await response.json();
    if (!response.ok) throw new Error(JSON.stringify(page));

    const bookmarkRows = page.tweets.map((tweet) => ({
      bookmark_source: bookmarkFolderId === "" ? "all_bookmarks" : "folder",
      folder_id: bookmarkFolderId || null,
      tweet_id: tweet.id,
      tweet_url: tweet.url ?? null,
      text: tweet.text,
      author_id: tweet.author?.id ?? null,
      author_username: tweet.author?.username ?? null,
      author_name: tweet.author?.name ?? null,
      author_followers: tweet.author?.followers ?? null,
      author_verified: tweet.author?.verified ?? null,
      author_profile_picture: tweet.author?.profilePicture ?? null,
      created_at: tweet.createdAt ?? null,
      like_count: tweet.likeCount ?? null,
      reply_count: tweet.replyCount ?? null,
      retweet_count: tweet.retweetCount ?? null,
      quote_count: tweet.quoteCount ?? null,
      view_count: tweet.viewCount ?? null,
      media_urls: (tweet.media ?? []).map((item) => item.mediaUrl).filter(Boolean),
      page_index: pageIndex,
      page_cursor: pageCursor,
      next_cursor: page.next_cursor,
      has_next_page: page.has_next_page,
    }));
    for (const row of bookmarkRows) process.stdout.write(`${JSON.stringify(row)}\n`);

    if (!page.has_next_page || page.next_cursor === "") break;
    pageCursor = page.next_cursor;
  }
  ```

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

  bookmark_folder_id = ""  # Set to a folder ID to export one folder.
  page_cursor = ""

  for page_index in range(3):
      params = {}
      if bookmark_folder_id:
          params["folderId"] = bookmark_folder_id
      if page_cursor:
          params["cursor"] = page_cursor

      response = requests.get(
          "https://xquik.com/api/v1/x/bookmarks",
          params=params,
          headers={"x-api-key": "xq_YOUR_KEY_HERE"},
      )
      page = response.json()
      if not response.ok:
          raise RuntimeError(page)

      for tweet in page["tweets"]:
          bookmark_row = {
              "bookmark_source": "all_bookmarks" if not bookmark_folder_id else "folder",
              "folder_id": bookmark_folder_id or None,
              "tweet_id": tweet["id"],
              "tweet_url": tweet.get("url"),
              "text": tweet["text"],
              "author_id": (tweet.get("author") or {}).get("id"),
              "author_username": (tweet.get("author") or {}).get("username"),
              "author_name": (tweet.get("author") or {}).get("name"),
              "author_followers": (tweet.get("author") or {}).get("followers"),
              "author_verified": (tweet.get("author") or {}).get("verified"),
              "author_profile_picture": (tweet.get("author") or {}).get("profilePicture"),
              "created_at": tweet.get("createdAt"),
              "like_count": tweet.get("likeCount"),
              "reply_count": tweet.get("replyCount"),
              "retweet_count": tweet.get("retweetCount"),
              "quote_count": tweet.get("quoteCount"),
              "view_count": tweet.get("viewCount"),
              "media_urls": [
                  item["mediaUrl"] for item in tweet.get("media", []) if item.get("mediaUrl")
              ],
              "page_index": page_index,
              "page_cursor": page_cursor,
              "next_cursor": page["next_cursor"],
              "has_next_page": page["has_next_page"],
          }
          print(json.dumps(bookmark_row, separators=(",", ":")))

      if not page["has_next_page"] or not page["next_cursor"]:
          break
      page_cursor = page["next_cursor"]
  ```
</CodeGroup>

## Bookmarks handoff

Use `GET /x/bookmarks` when a reading list, CRM, research queue, or agent needs
saved tweets from the authenticated account. The examples write JSON Lines rows
with bookmark source, folder ID, tweet ID, tweet URL, text, author ID,
username, display name, follower count, verified state, profile image URL,
engagement counts, media URLs, and cursor fields. Store the last saved
`next_cursor` per folder. Resume each bookmark export from that checkpoint.

<CardGroup cols={2}>
  <Card title="All saved tweets" icon="bookmark">
    Omit `folderId` when the workflow needs every bookmarked tweet visible to
    the connected account.
  </Card>

  <Card title="Folder export" icon="folder">
    Call [Bookmark Folders](/api-reference/x/bookmark-folders) first, then pass
    its `folder_id` as `folderId`. Folders need X Premium.
  </Card>

  <Card title="Cursor checkpoint" icon="arrow-right">
    Store `has_next_page` and `next_cursor` per folder. Pass `next_cursor` back
    as `cursor` only when `has_next_page` is true.
  </Card>

  <Card title="Account-scoped queue" icon="lock-keyhole">
    Store saved-tweet rows only in systems scoped to that account.
  </Card>
</CardGroup>

## Twitter bookmark API questions

### How do I export Twitter bookmarks?

Run the Node.js or Python example for each cursor page. Both examples create a
structured JSON Lines record for every saved tweet. Each row keeps tweet text, authors, likes,
replies, reposts, views, media URLs, and folder context.

Convert the completed rows to a CSV file after collection. Use `tweet_id` as
the stable key. Create a bookmarks page in a spreadsheet or research tool.
Choose destination columns before sending rows to Notion databases or CRMs.

### How does bookmark authentication work?

Connect the X account that owns the saved tweets. Include that account's Xquik
API key in every request. The endpoint never accepts another username. X treats bookmarks
as private to their owner.

### Can I export another user's bookmarks?

No. Bookmarks are private to the connected account. Keep every export scoped
to that account. Do not publish saved tweets or private folder names.

### How do bookmark folders work?

Call [Get bookmark folders](/api-reference/x/bookmark-folders) first. Pass the
returned `folder_id` as `folderId`. Omit `folderId` to request all visible
bookmarks from the connected account. Store one `next_cursor` checkpoint per
folder. Keep `folder_id` when importing records into Notion databases.

### What limits a bookmark export?

You receive the bookmarks that the connected account can view. Low credits may
reduce the returned row count. A `429` response includes retry guidance.
`424 account_required` means you have no connected X account. Other `424`,
`502` & `503` responses are temporary read failures.

### How should I store a bookmark export?

Store the connected account ID beside each row. Keep private folder names out of public logs.
Delete temporary files after the approved handoff finishes.

## Query parameters

<ParamField query="folderId" type="string">
  Bookmark folder ID. Omit to return all bookmarks.
</ParamField>

<ParamField query="cursor" type="string">
  Pass the previous response's `next_cursor` to fetch another page. Omit it on
  the initial request.
</ParamField>

## Which saved-feed endpoint?

<CardGroup cols={2}>
  <Card title="Saved tweets" icon="bookmark">
    Use `GET /x/bookmarks` for bookmarked tweets from the connected account.
  </Card>

  <Card title="Bookmark folders" icon="folder">
    Use [`GET /x/bookmarks/folders`](/api-reference/x/bookmark-folders) to find
    folder IDs before a folder-specific bookmark export.
  </Card>

  <Card title="Home timeline" icon="house">
    Use [`GET /x/timeline`](/api-reference/x/timeline) for the connected
    account's home feed instead of saved tweets.
  </Card>

  <Card title="Notifications" icon="bell">
    Use [`GET /x/notifications`](/api-reference/x/notifications) for inbox
    activity rows from the connected account.
  </Card>
</CardGroup>

## Headers

<ParamField header="x-api-key" type="string" required>
  Your API key. Session cookie authentication is also supported.
</ParamField>

## Response

### 200 OK

<ResponseField name="tweets" type="object[]">
  Array of bookmarked tweets.
  **Tweet object fields.**

  <ResponseField name="id" type="string">This value contains the tweet ID.</ResponseField>
  <ResponseField name="text" type="string">This value contains the tweet text.</ResponseField>
  <ResponseField name="type" type="string">This value identifies the tweet type. X may omit it.</ResponseField>
  <ResponseField name="createdAt" type="string">This value contains the ISO 8601 creation time. X may omit it.</ResponseField>
  <ResponseField name="isNoteTweet" type="boolean">X sets this flag for long-form Note Tweets. X may omit it.</ResponseField>
  <ResponseField name="isPinned" type="boolean">Whether the author pinned this post to their profile. Omitted if unavailable.</ResponseField>
  <ResponseField name="likeCount" type="number">This value records the like count. X may omit it.</ResponseField>
  <ResponseField name="retweetCount" type="number">This value records the repost count. X may omit it.</ResponseField>
  <ResponseField name="replyCount" type="number">This value records the reply count. X may omit it.</ResponseField>
  <ResponseField name="quoteCount" type="number">This value records the quote count. X may omit it.</ResponseField>
  <ResponseField name="viewCount" type="number">This value records the view count. X may omit it.</ResponseField>
  <ResponseField name="bookmarkCount" type="number">This value records the bookmark count. X may omit it.</ResponseField>
  <ResponseField name="url" type="string">This value contains the tweet URL. X may omit it.</ResponseField>
  <ResponseField name="lang" type="string">This value contains the tweet language code. X may omit it.</ResponseField>
  <ResponseField name="isReply" type="boolean">X sets this flag for replies. X may omit it.</ResponseField>
  <ResponseField name="inReplyToId" type="string">This value identifies the replied-to tweet. X may omit it.</ResponseField>
  <ResponseField name="inReplyToUserId" type="string">This value identifies the replied-to user. X may omit it.</ResponseField>
  <ResponseField name="inReplyToUsername" type="string">This value identifies the replied-to username. X may omit it.</ResponseField>
  <ResponseField name="conversationId" type="string">This value contains the conversation ID. X may omit it.</ResponseField>
  <ResponseField name="source" type="string">This value identifies the posting client. X may omit it.</ResponseField>
  <ResponseField name="displayTextRange" type="number[]">This array contains the rendered text offsets. X may omit it.</ResponseField>
  <ResponseField name="isLimitedReply" type="boolean">X sets this flag when replies are limited. X may omit it.</ResponseField>
  <ResponseField name="isQuoteStatus" type="boolean">X sets this flag for quote tweets. X may omit it.</ResponseField>
  <ResponseField name="isRetweet" type="boolean">Whether this row is a retweet. `text` carries the original post in full.</ResponseField>
  <ResponseField name="entities" type="object">This object contains parsed tweet entities. X may omit it.</ResponseField>
  <ResponseField name="contentDisclosure" type="object">This object contains paid partnership and AI media labels. X may omit it.</ResponseField>

  <ResponseField name="author" type="object">
    This object contains the tweet author profile. X may omit it.
    **Author object fields.**
    <ResponseField name="id" type="string">Author user ID.</ResponseField>
    <ResponseField name="username" type="string">Author X username.</ResponseField>
    <ResponseField name="name" type="string">Author display name.</ResponseField>
    <ResponseField name="followers" type="number">This value records the follower count. X may omit it.</ResponseField>
    <ResponseField name="verified" type="boolean">X sets this flag for verified authors. X may omit it.</ResponseField>
    <ResponseField name="profilePicture" type="string">This value contains the profile picture URL. X may omit it.</ResponseField>
  </ResponseField>

  <ResponseField name="media" type="object[]">
    This array contains media attachments. X may omit it.
    **Media item fields.**
    <ResponseField name="mediaUrl" type="string">Direct media URL.</ResponseField>
    <ResponseField name="videoVariants" type="object[]">This array contains video URLs, bitrates, and content types. X omits it for images.</ResponseField>
    <ResponseField name="type" type="string">Media type.</ResponseField>
    <ResponseField name="url" type="string">Shortened URL from the tweet text.</ResponseField>
  </ResponseField>

  <ResponseField name="quoted_tweet" type="object">This object contains the quoted tweet. X may omit it.</ResponseField>
  <ResponseField name="retweeted_tweet" type="object">This object contains the original repost. X may omit it.</ResponseField>
</ResponseField>

<ResponseField name="has_next_page" type="boolean">
  Whether more results are available.
</ResponseField>

<ResponseField name="next_cursor" type="string">
  Opaque cursor for the next page. Empty string when no more results.
</ResponseField>

<Note>
  **Related.** [Bookmark Folders](/api-reference/x/bookmark-folders) · [Timeline](/api-reference/x/timeline)
</Note>

<div className="related-api-links">
  <Accordion title="Related timeline, bookmark & notification APIs" icon="link">
    * Account feeds: [Home timeline](/api-reference/x/timeline) · [Notifications](/api-reference/x/notifications) · [Mentions](/api-reference/x/user-mentions)
    * Saved and private: [Bookmarks](/api-reference/x/bookmarks) · [Bookmark folders](/api-reference/x/bookmark-folders) · [DM history](/api-reference/x/dm-history)
  </Accordion>
</div>


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