Fetch and Extract an X Post

Site x.comTask fetch-x-postVersion v11Updated Aug 29, 2026Category social

Load an X status URL, or resolve a relevant post from an account timeline, then deterministically extract post text, attached image and video metadata, page signals, and visible access restrictions for detailed post analysis. This skill was captured from a live agent session on x.com and is published here as a reusable recipe for agents.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Fetch an individual X post and return whether it was accessible, its text when available, attached image and video metadata, page signals, and visible restriction or error signals. When only an account and a content clue are available, resolve candidate status URLs from the account's /media or /with_replies route before opening the selected post. Preserve enough structured media information to support a detailed explanation without confusing replies or related posts with the requested post.

Use Cases

Use when the caller supplies an X status URL or opaque status ID. It also applies when a relevant post must be found among an account's media posts or surrounding posts using text, timestamp, attached-image, or neighboring-post clues. This recipe is for reading a single post and its directly attached media, including posts relevant to researching equipment, products, or other detailed subject information; it is not for broad timeline searches or collecting replies.

Automation Flow

  1. If a status URL or status ID is available, navigate directly to https://x.com/{username}/status/{status-id}?s=20 (or use the caller-provided status URL), waiting for domcontentloaded. Do not visit the homepage or use the search box first.
  2. If no status ID is available, navigate directly to https://x.com/{username}/media for image/media clues, or https://x.com/{username}/with_replies when surrounding posts or neighboring context is needed. Run the candidate evaluator below, select the candidate whose text, timestamp, image alt text, or neighboring context matches the clue, and read its opaque /status/{status-id} URL from statusUrl; never guess the ID. Navigate directly to that status URL and run the post evaluator.
  3. On the loaded status page, run this evaluate() in the same browser call/session:
(() => {
  const bodyText = (document.body?.innerText || '').trim();
  const articles = [...document.querySelectorAll('article')];
  const posts = articles.map((article, index) => {
    const tweetText = [...article.querySelectorAll('[data-testid="tweetText"]')]
      .map(node => (node.innerText || node.textContent || '').trim())
      .filter(Boolean);
    const links = [...article.querySelectorAll('a')]
      .map(a => ({ text: (a.innerText || '').trim(), href: a.href || '' }))
      .filter(link => link.text || link.href.includes('t.co'))
      .slice(0, 30);
    const imgs = [...article.querySelectorAll('img')]
      .map(img => ({ alt: img.alt || '', src: img.src || '', width: img.naturalWidth || null, height: img.naturalHeight || null }))
      .filter(image => image.src)
      .slice(0, 20);
    const vids = [...article.querySelectorAll('video')]
      .map(video => ({ src: video.currentSrc || video.src || '', poster: video.poster || '', width: video.videoWidth || null, height: video.videoHeight || null, duration: Number.isFinite(video.duration) ? video.duration : null, currentTime: video.currentTime || 0, paused: video.paused }));
    return { index, text: (article.innerText || '').trim(), tweetText, links, imgs, vids };
  });
  const restrictionPatterns = [
    /log in/i, /sign up/i, /create an account/i, /something went wrong/i,
    /this page doesn.?t exist/i, /post isn.?t available/i, /content warning/i,
    /account suspended/i, /rate limit/i, /unusual activity/i, /challenge/i, /robot/i
  ];
  const restrictions = restrictionPatterns.filter(pattern => pattern.test(bodyText)).map(pattern => pattern.source);
  const primary = posts[0] || null;
  return {
    url: location.href,
    success: Boolean(primary?.tweetText?.length),
    extractedPostText: primary?.tweetText?.join('\n') || null,
    posts,
    media: primary ? { images: primary.imgs, videos: primary.vids } : { images: [], videos: [] },
    restrictions,
    pageTitle: document.title || null
  };
})()
  1. For resolving candidates from /media or /with_replies, use this evaluator on that page:
(() => [...document.querySelectorAll('article')].map((article, index) => {
  const statusUrl = [...article.querySelectorAll('a[href*="/status/"]')]
    .map(a => a.href).find(href => /\/status\/\d+/.test(href)) || null;
  const text = (article.innerText || '').trim();
  const tweetText = [...article.querySelectorAll('[data-testid="tweetText"]')]
    .map(n => (n.innerText || n.textContent || '').trim()).filter(Boolean);
  const images = [...article.querySelectorAll('img')].map(img => ({ alt: img.alt || '', src: img.src || '', width: img.naturalWidth || null, height: img.naturalHeight || null })).filter(img => img.src);
  const videos = [...article.querySelectorAll('video')].map(video => ({ src: video.currentSrc || video.src || '', poster: video.poster || '', width: video.videoWidth || null, height: video.videoHeight || null, duration: Number.isFinite(video.duration) ? video.duration : null }));
  return { index, statusUrl, text, tweetText, images, videos };
}))()
  1. Use the first article on a status page as the requested post and treat later articles as replies or related content. If media metadata is incomplete, optionally open a[aria-label="View media"], wait briefly, and rerun the post evaluator. Attached photo routes may also be inspected directly at https://x.com/{username}/status/{status-id}/photo/{n}.
  2. For a video whose meaning is not represented by text or metadata, inspect a small set of temporal positions (for example 0%, 25%, 50%, 75%, and near the end): pause the first article video, set currentTime, wait for the frame to render, and inspect the resulting frame or screenshot. Do not infer visual claims from duration or poster metadata alone. Avoid exhaustive frame-by-frame sampling.
  3. Combine the evaluator result with browser navigation status. Report tool-level failures separately from page-level restrictions; successful navigation without a matching article is not successful extraction.

Possible Friction Points

  • The primary post is represented by an article; its text is normally in [data-testid="tweetText"]. Additional articles may be replies or related content, so use the first article for a requested status page.
  • Account media posts are reachable at https://x.com/{username}/media, while surrounding posts and replies are reachable at https://x.com/{username}/with_replies. Status links embedded in article cards expose the opaque ID needed for a direct status URL; resolve and read that link rather than deriving or guessing the ID.
  • Images attached to a post are exposed as article img elements. Preserve their alt, src, and natural dimensions; photo-specific routes use /photo/{n} when direct inspection is necessary.
  • Videos may not expose a usable video element until playback or the media viewer is opened. Preserve currentSrc/src, poster, intrinsic dimensions, duration, and playback state. For detailed explanations, sample a few meaningful timestamps rather than repeatedly seeking through the entire video.
  • For equipment or other detailed subject research, preserve the full post text, links, and image/video metadata; images and video may contain information not represented in the text.
  • X may render a login wall, unavailable-post message, consent screen, rate-limit page, or anti-bot challenge without exposing tweet text. Return success: false and the visible signals in that case.
  • The status ID is opaque and must come from the caller's URL or be resolved from a status link in an account timeline; never guess it from the username.
  • The s=20 query parameter is optional for direct status navigation and should be preserved when supplied.
  • A browser navigation status is a tool-level result, while success indicates that post text was actually extracted from the DOM.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=x.com&task=fetch-x-post