{
  "data": {
    "id": "pCbOAmk0HXdMNmZOp",
    "actId": "Sf31Jgh0Kbad3YVuJ",
    "userId": "IYgBDiXtHOijAaUel",
    "startedAt": "2026-10-29T06:56:26.694Z",
    "finishedAt": "2026-10-29T06:56:38.012Z",
    "status": "SUCCEEDED",
    "meta": {
      "origin": "CLI"
    },
    "stats": {
      "durationMillis": 11230,
      "runTimeSecs": 11.23,
      "computeUnits": 0.012477777777777779,
      "imageSizeBytes": 342246400
    },
    "options": {
      "useCache": true,
      "betaPackages": false,
      "memoryMbytes": 4096,
      "diskMbytes": 8192
    },
    "createdByOrganizationMemberUserId": "QYo53EZkJk0prePWY",
    "exitCode": 0,
    "inputSchema": "{\n  \"title\": \"X (Twitter) Profile & Post Engagement Extractor\",\n  \"description\": \"Collect any public X profile and its posts without login, cookies or API keys.\",\n  \"type\": \"object\",\n  \"schemaVersion\": 1,\n  \"properties\": {\n    \"usernames\": {\n      \"title\": \"X usernames, profile links or numeric ids\",\n      \"type\": \"array\",\n      \"description\": \"Accounts to collect. Paste handles like `elonmusk`, `@apify`, profile links like `https://x.com/elonmusk`, numeric ids like `11348282` or `id:11348282`. Handles, links and ids can be mixed.\",\n      \"editor\": \"stringList\",\n      \"default\": [],\n      \"prefill\": [\n        \"NASA\"\n      ],\n      \"example\": [\n        \"NASA\",\n        \"https://x.com/apify\",\n        \"id:11348282\"\n      ]\n    },\n    \"include\": {\n      \"title\": \"What to collect\",\n      \"type\": \"array\",\n      \"description\": \"Pick one or more data groups. Everything works without cookies. `topPosts` is engagement-ranked from the candidate pool (X serves no public Top tab to guests). `replies` and `highlights` are best-effort: X sometimes refuses those guest timelines.\",\n      \"editor\": \"select\",\n      \"items\": {\n        \"type\": \"string\",\n        \"enum\": [\n          \"profileInfo\",\n          \"latestPosts\",\n          \"topPosts\",\n          \"media\",\n          \"replies\",\n          \"highlights\",\n          \"about\"\n        ],\n        \"enumTitles\": [\n          \"Profile info (bio, followers, verification)\",\n          \"Latest posts (newest first)\",\n          \"Top posts (engagement-ranked)\",\n          \"Media posts only (photos, videos, GIFs)\",\n          \"Replies the account sent (best effort)\",\n          \"Highlights tab (cursor-paged)\",\n          \"About panel (login-only; reported as unavailable)\"\n        ]\n      },\n      \"uniqueItems\": true,\n      \"default\": [\n        \"profileInfo\",\n        \"latestPosts\"\n      ],\n      \"prefill\": [\n        \"profileInfo\"\n      ]\n    },\n    \"postUrls\": {\n      \"title\": \"Specific post URLs or ids\",\n      \"type\": \"array\",\n      \"description\": \"Optional. Collect these exact posts (focal post plus up to 3 replies each) in addition to the profiles above. Accepts `https://x.com/user/status/123` or a numeric post id.\",\n      \"editor\": \"stringList\",\n      \"default\": [],\n      \"example\": [\n        \"https://x.com/NASA/status/2114801524358406570\"\n      ]\n    },\n    \"maxPostsPerProfile\": {\n      \"title\": \"Maximum posts per profile\",\n      \"type\": \"integer\",\n      \"description\": \"Target number of posts per account (1-1000). The actor follows X's guest timeline bottom cursor back to X's ceiling of about 3,200 posts; guest search top-ups are best effort. Filter-heavy runs page until the target number of matching rows is found or the source ceiling is reached. A pinned post can be added beyond this cap when it passes your filters; it stays in chronological order for latest and in ranked order for top.\",\n      \"default\": 50,\n      \"prefill\": 3,\n      \"minimum\": 1,\n      \"maximum\": 1000\n    },\n    \"includeReplies\": {\n      \"title\": \"Include replies\",\n      \"type\": \"boolean\",\n      \"description\": \"Keep replies the account sent. Best effort: X only serves the reply timeline to logged-out callers on some attempts.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"includeRetweets\": {\n      \"title\": \"Include reposts\",\n      \"type\": \"boolean\",\n      \"description\": \"Keep reposts in the results. Turn off to get only posts the account wrote itself.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"includeHighlights\": {\n      \"title\": \"Include Highlights tab\",\n      \"type\": \"boolean\",\n      \"description\": \"Also collect the account's curated Highlights tab. The guest legacy userId query pages the whole set through its bottom cursor (observed 100-1,097 rows on large accounts); the live one-page query remains a fallback.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"dateFrom\": {\n      \"title\": \"Posted after\",\n      \"type\": \"string\",\n      \"description\": \"Optional. Only keep posts published on or after this date.\",\n      \"editor\": \"datepicker\"\n    },\n    \"dateTo\": {\n      \"title\": \"Posted before\",\n      \"type\": \"string\",\n      \"description\": \"Optional. Only keep posts published on or before this date.\",\n      \"editor\": \"datepicker\"\n    },\n    \"language\": {\n      \"title\": \"Post language\",\n      \"type\": \"string\",\n      \"description\": \"Optional. Keep only posts detected in this language, e.g. `en`, `es`, `ja`. Locale forms such as `en-US` or `pt-BR` are normalized to their two-letter code in code, so old tasks keep working.\",\n      \"editor\": \"textfield\"\n    },\n    \"minimumLikes\": {\n      \"title\": \"Minimum likes\",\n      \"type\": \"integer\",\n      \"description\": \"Optional. Drop posts with fewer likes than this.\",\n      \"minimum\": 0,\n      \"maximum\": 1000000000\n    },\n    \"minimumRetweets\": {\n      \"title\": \"Minimum reposts\",\n      \"type\": \"integer\",\n      \"description\": \"Optional. Drop posts with fewer reposts than this.\",\n      \"minimum\": 0,\n      \"maximum\": 1000000000\n    },\n    \"minimumReplies\": {\n      \"title\": \"Minimum replies\",\n      \"type\": \"integer\",\n      \"description\": \"Optional. Drop posts with fewer replies than this.\",\n      \"minimum\": 0,\n      \"maximum\": 1000000000\n    },\n    \"minimumViews\": {\n      \"title\": \"Minimum views\",\n      \"type\": \"integer\",\n      \"description\": \"Optional. Drop posts with fewer views than this. Posts whose view count X does not expose are dropped when this is set.\",\n      \"minimum\": 0,\n      \"maximum\": 1000000000000\n    },\n    \"minimumQuotes\": {\n      \"title\": \"Minimum quotes\",\n      \"type\": \"integer\",\n      \"description\": \"Optional. Drop posts with fewer quotes than this. Posts whose quote count X does not expose are dropped when this is set.\",\n      \"minimum\": 0,\n      \"maximum\": 1000000000\n    },\n    \"onlyWithMedia\": {\n      \"title\": \"Only posts with images, videos or GIFs\",\n      \"type\": \"boolean\",\n      \"description\": \"Keep only posts that contain media.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"mediaType\": {\n      \"title\": \"Media type\",\n      \"type\": \"string\",\n      \"description\": \"Optional. Keep only posts with this media type. `media` in What to collect already limits rows to media posts.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"any\",\n        \"photos\",\n        \"videos\",\n        \"gifs\"\n      ],\n      \"enumTitles\": [\n        \"Any media\",\n        \"Photos only\",\n        \"Videos only\",\n        \"GIFs only\"\n      ],\n      \"default\": \"any\",\n      \"prefill\": \"any\"\n    },\n    \"onlyVerified\": {\n      \"title\": \"Only verified accounts\",\n      \"type\": \"boolean\",\n      \"description\": \"Skip accounts that do not carry a verified badge.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"verifiedType\": {\n      \"title\": \"Verification type\",\n      \"type\": \"string\",\n      \"description\": \"Optional. Keep only accounts with this verification type. X and FxTwitter labels are normalized in code.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"any\",\n        \"blue\",\n        \"business\",\n        \"government\"\n      ],\n      \"enumTitles\": [\n        \"Any verification\",\n        \"Blue / individual\",\n        \"Business / organization\",\n        \"Government\"\n      ],\n      \"default\": \"any\",\n      \"prefill\": \"any\"\n    },\n    \"minFollowers\": {\n      \"title\": \"Minimum followers\",\n      \"type\": \"integer\",\n      \"description\": \"Optional. Skip accounts below this follower count. 0 means any.\",\n      \"default\": 0,\n      \"prefill\": 0,\n      \"minimum\": 0,\n      \"maximum\": 1000000000\n    },\n    \"maxFollowers\": {\n      \"title\": \"Maximum followers\",\n      \"type\": \"integer\",\n      \"description\": \"Optional. Skip accounts above this follower count. 0 means any.\",\n      \"default\": 0,\n      \"prefill\": 0,\n      \"minimum\": 0,\n      \"maximum\": 1000000000\n    },\n    \"minAccountAgeDays\": {\n      \"title\": \"Minimum account age (days)\",\n      \"type\": \"integer\",\n      \"description\": \"Optional. Skip accounts younger than this. 0 means any. Accounts without a public join date are skipped when this is set.\",\n      \"default\": 0,\n      \"prefill\": 0,\n      \"minimum\": 0,\n      \"maximum\": 100000\n    },\n    \"withWebsiteOnly\": {\n      \"title\": \"Only accounts with a website\",\n      \"type\": \"boolean\",\n      \"description\": \"Skip accounts that expose no website link in their profile or bio.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"bioKeywords\": {\n      \"title\": \"Bio keywords\",\n      \"type\": \"array\",\n      \"description\": \"Optional. Keep only accounts whose name or bio contains every keyword (case-insensitive).\",\n      \"editor\": \"stringList\",\n      \"default\": []\n    },\n    \"activeWithinDays\": {\n      \"title\": \"Active within days\",\n      \"type\": \"integer\",\n      \"description\": \"Optional. Skip accounts whose newest public post is older than this many days. 0 means any. Requires reading the latest posts to evaluate.\",\n      \"default\": 0,\n      \"prefill\": 0,\n      \"minimum\": 0,\n      \"maximum\": 36500\n    },\n    \"enrichEngagement\": {\n      \"title\": \"Enrich engagement per post\",\n      \"type\": \"boolean\",\n      \"description\": \"Refresh each collected post through its public status page for views, bookmarks and complete media. Adds one request per post. Turn off for the cheapest profile-only runs.\",\n      \"default\": true,\n      \"prefill\": false\n    },\n    \"proxyConfiguration\": {\n      \"title\": \"Proxy configuration\",\n      \"type\": \"object\",\n      \"description\": \"Optional. The actor works without a proxy and tries a direct connection first. Use this only if you want to override the built-in direct -> Apify AUTO escalation.\",\n      \"editor\": \"proxy\",\n      \"prefill\": {\n        \"useApifyProxy\": false\n      },\n      \"sectionCaption\": \"Advanced\",\n      \"sectionDescription\": \"Optional extras. The actor works fully without these.\"\n    }\n  },\n  \"required\": []\n}",
    "readme": "# X (Twitter) Profile & Post Engagement Extractor\r\n\r\nCollect public X profiles and posts without login, cookies, developer keys or API tokens. The actor returns a profile row, post rows with engagement and media, and explicit status rows when X limits or hides something.\r\n\r\n## What it collects\r\n\r\n- **Profile header** from the public profile page: handle, display name, bio, bio links and emails, location, website, join date, follower/following/post/media/like counters, verification type, avatar, banner, public birth date, pinned post and professional label.\r\n- **Latest posts** from the profile's server-rendered originals timeline, strictly newest first, topped up through the cursor-paged timeline when the target is higher than the 5-9 server-rendered rows. A pinned post is marked `isPinned` but stays in its chronological position (and may exceed the cap by one).\r\n- **Top posts** ranked by total engagement. X exposes no public Top tab to logged-out callers, so the actor combines the guest search Top product with the profile timeline candidate pool and ranks the merged set locally; a pinned post is not promoted above its earned rank.\r\n- **Media posts** by filtering the collected timelines to photos, videos and GIFs. X does not serve a full media tab to logged-out callers. Filter-heavy runs page the timeline until the requested number of matching rows is found or the source ceiling is reached, so rare GIF matches are not missed by a fixed buffer.\r\n- **Replies and Highlights** as best-effort groups. Highlights are cursor-paged through the legacy guest query, so large curated sets return their full observed depth; replies remain limited. X sometimes refuses those guest timelines; the actor reports `repliesLimited` or `highlightsLimited` instead of pretending the account has none.\r\n- **Specific post URLs** through `postUrls`: the focal post plus up to three server-rendered replies per URL.\r\n- **Derived activity metrics** on profile rows: account age, followers/following ratio, posts per day, last post date, days since the last post, recent average likes/replies/reposts/views and a recent engagement percentage.\r\n- **Provenance and timestamps** on every row: `type`, `recordType`, `sourceUrl`, `collectedAt` and `scrapedAt`.\r\n\r\nThe actor never signs in, never asks for cookies and never uses a user-supplied token.\r\n\r\n## Input\r\n\r\n| Field | Type | Default | Description |\r\n|---|---|---|---|\r\n| `usernames` | array | – | Optional; supply this, `postUrls`, or both. Handles (`NASA`, `@NASA`), profile links (`https://x.com/NASA`), numeric ids (`11348282`, `id:11348282`) or `https://x.com/i/user/11348282`. |\r\n| `include` | multi-select | `[\"profileInfo\", \"latestPosts\"]` | `profileInfo`, `latestPosts`, `topPosts`, `media`, `replies`, `highlights`; legacy `about` is accepted but reported as unavailable. Groups are additive. |\r\n| `postUrls` | array | `[]` | Exact post URLs or ids; can run alone or alongside profiles. Each returns the focal post and up to 3 replies. |\r\n| `maxPostsPerProfile` | integer 1-1000 | `50` | Post target per account. The actor follows X's guest timeline bottom cursor (X's own ceiling is about 3,200 posts). When a target is still unmet, older posts are filled from search windows only when X serves them. |\r\n| `includeReplies` | boolean | `false` | Keep replies the account sent (best effort). |\r\n| `includeRetweets` | boolean | `false` | Keep reposts. |\r\n| `includeHighlights` | boolean | `false` | Also collect the Highlights tab. Cursor-paged up to `maxPostsPerProfile`; observed depths include 113, 287 and 1,097 rows on different accounts. |\r\n| `dateFrom`, `dateTo` | date picker | – | Client-side post date window. `dateTo` includes the whole day. |\r\n| `language` | text | – | Post language, e.g. `en`, `es`, `ja`. Locale forms such as `en-US` and `pt-BR` are normalized to `en`/`pt` in code, so old tasks keep working. |\r\n| `minimumLikes`, `minimumRetweets`, `minimumReplies`, `minimumViews`, `minimumQuotes` | integer ≥ 0 | – | Client-side engagement minimums. Posts missing the requested count are dropped when the minimum is set. |\r\n| `onlyWithMedia` | boolean | `false` | Keep only posts with photos, videos or GIFs. |\r\n| `mediaType` | select | `any` | `any`, `photos`, `videos`, `gifs`. |\r\n| `onlyVerified` | boolean | `false` | Skip accounts without a verification badge. |\r\n| `verifiedType` | select | `any` | `any`, `blue`, `business`, `government`. X and mirror labels are normalized in code. |\r\n| `minFollowers`, `maxFollowers` | integer ≥ 0 | `0` | Account-level follower range. `0` means any. |\r\n| `minAccountAgeDays` | integer ≥ 0 | `0` | Skip accounts younger than this. Accounts without a public join date are skipped when set. |\r\n| `withWebsiteOnly` | boolean | `false` | Skip accounts with no website in the profile or bio. |\r\n| `bioKeywords` | array | `[]` | Keep accounts whose name or bio contains every keyword (case-insensitive). |\r\n| `activeWithinDays` | integer ≥ 0 | `0` | Skip accounts whose newest public post is older than this. |\r\n| `enrichEngagement` | boolean | `true` | Refresh each collected post through its public status page for views, bookmarks and complete media. One request per post; turn off for the cheapest profile-only runs. |\r\n| `proxyConfiguration` | proxy | – | Optional override. The actor works without a proxy and tries the direct connection first. |\r\n\r\n### Examples\r\n\r\nProfile only (the daily prefill shape):\r\n\r\n```json\r\n{\r\n  \"usernames\": [\"NASA\"],\r\n  \"include\": [\"profileInfo\"],\r\n  \"maxPostsPerProfile\": 3,\r\n  \"enrichEngagement\": false\r\n}\r\n```\r\n\r\nProfile plus latest and top posts:\r\n\r\n```json\r\n{\r\n  \"usernames\": [\"NASA\", \"https://x.com/apify\"],\r\n  \"include\": [\"profileInfo\", \"latestPosts\", \"topPosts\"],\r\n  \"maxPostsPerProfile\": 100\r\n}\r\n```\r\n\r\nHigh-engagement photo posts from a date range:\r\n\r\n```json\r\n{\r\n  \"usernames\": [\"NASA\"],\r\n  \"include\": [\"latestPosts\"],\r\n  \"mediaType\": \"photos\",\r\n  \"minimumLikes\": 5000,\r\n  \"minimumViews\": 1000000,\r\n  \"dateFrom\": \"2026-01-01\",\r\n  \"dateTo\": \"2026-10-27\"\r\n}\r\n```\r\n\r\nLead-gen profile filter:\r\n\r\n```json\r\n{\r\n  \"usernames\": [\"apify\", \"NASA\"],\r\n  \"include\": [\"profileInfo\"],\r\n  \"minFollowers\": 10000,\r\n  \"minAccountAgeDays\": 365,\r\n  \"withWebsiteOnly\": true,\r\n  \"bioKeywords\": [\"space\"]\r\n}\r\n```\r\n\r\nSpecific posts:\r\n\r\n```json\r\n{\r\n  \"usernames\": [\"NASA\"],\r\n  \"include\": [\"profileInfo\"],\r\n  \"postUrls\": [\"https://x.com/NASA/status/2114801524358406570\"]\r\n}\r\n```\r\n\r\n## Output\r\n\r\nEvery row has `type` (`profile`, `post` or `status`), `recordType`, `collectedAt` and `scrapedAt`. Profile and post rows, and status rows with a known account or post URL, also carry `sourceUrl`. Empty fields are omitted recursively: the dataset never contains `null`, `\"\"`, `[]` or `{}`.\r\n\r\n### Profile row\r\n\r\nKeeps the original fields: `id`, `username`, `name`, `bio`, `bioLinks`, `bioEmails`, `location`, `website`, `joinedAt`, `birthDate`, `followers`, `following`, `postCount`, `mediaCount`, `likesGiven`, `listedCount`, `isBlueVerified`, `verified`, `verifiedType`, `label`, `isProtected`, `avatarUrl`, `bannerUrl`, `pinnedPostId`, `pinnedTweetId`, `pinnedTweet`, `mediaTweets`, `withReplies`, `profileStatus`, `collectedAt`.\r\n\r\nAdds: `profileUrl`, `websiteDisplayUrl`, `bioMentions`, `accountAgeDays`, `followersToFollowingRatio`, `postsPerDay`, `lastPostAt`, `daysSinceLastPost`, `recentAvgLikes`, `recentAvgReplies`, `recentAvgReposts`, `recentAvgViews`, `recentEngagementRatePct`, `professionalType`, `professionalCategory`, `affiliateLabel`, `highlightCount`, `recordType`, `sourceUrl`, `scrapedAt`.\r\n\r\n`websiteDisplayUrl` is the label X shows for the profile's website entity (for example `nasa.gov`). Bio links stay in `bioLinks` and are never reused as the website label.\r\n\r\nConditional fields are omitted when X does not expose them. `listedCount` is not guaranteed by current cookieless payloads.\r\n\r\n### Post row\r\n\r\nKeeps the original fields: `id`, `url`, `text`, `createdAt`, `language`, `replyCount`, `retweetCount`, `likeCount`, `quoteCount`, `bookmarkCount`, `viewCount`, `engagementRate`, `isReply`, `isRetweet`, `isQuote`, `isNoteTweet`, `isPinned`, `conversationId`, `threadRootId`, `threadPosition`, `inReplyToTweetId`, `inReplyToUserId`, `inReplyToUsername`, `hashtags`, `mentions`, `urls`, `cashtags`, `media`, `quotedTweet`, `quotedTweetId`, `poll`, `card`, `possiblySensitive`, `place`, `postedFrom`, `withheldInCountries`, `isEdited`, `editControl`, `newsActionType`, `parentTweet`, `author`, `username`, `collectedAt`.\r\n\r\nAdds: `totalEngagement`, `engagementPerView`, `mediaTypes`, `source`, `isHighlight`, `authorId`, `authorUsername`, `authorName`, `recordType`, `sourceUrl`, `scrapedAt`.\r\n\r\n`source` is provenance: `profileLatest`, `profileTop`, `profileHighlight`, `profileReplies`, `syndication`, `search` or `status`.\r\n\r\n`conversationId` and `threadRootId` are set on `postUrls` replies from the focal post when the status page omits them, so every reply can be grouped by conversation.\r\n\r\n`totalEngagement` is emitted only when likes, reposts, replies and quotes are all real numbers. `engagementPerView` additionally needs a positive view count. No count is invented.\r\n\r\n**Media item:** `type` (`photo` / `video` / `gif`), `url`, `previewImageUrl`, `width`, `height`, `durationSeconds`, `viewCount`, `altText`, `variants`, and where X provides them `mediaKey`, `allowDownload`, `mediaAvailability`, `focusRects`. Video `url` prefers the best MP4 at or below 1080p; the full variant list (including 4K) remains in `variants`.\r\n\r\n### Status row\r\n\r\nStatus rows explain a skipped, protected, missing, empty or limited account:\r\n\r\n| Status | Meaning |\r\n|---|---|\r\n| `noInput` | No usable usernames or post URLs were supplied. |\r\n| `invalidUsername` | A username/link/id could not be parsed. |\r\n| `notFound` | The account does not exist, was removed or is suspended. |\r\n| `rateLimited` | X temporarily limited the profile request. |\r\n| `skipped` | An account-level filter excluded the account. The message names the filter. |\r\n| `protected` | The account is protected; only its public header is returned. |\r\n| `repliesLimited` | Replies were requested but X served none or fewer than the target this run; `achieved` and `target` are included. |\r\n| `highlightsLimited` | Highlights were requested but X returned no rows or refused the request this run. |\r\n| `noMediaPosts` | Media mode found no matching photos, videos or GIFs. |\r\n| `noPosts` | Posts were collected but every one was filtered out. |\r\n| `noVisiblePosts` | The account exists but X shows no public posts. |\r\n| `timelineUnavailable` | The profile loaded but its posts could not be collected. |\r\n| `searchLimited` | Fewer posts than requested were available; `achieved` and `target` are included. |\r\n| `aboutUnavailable` | A legacy task requested X's login-only About panel. |\r\n| `error` | An unexpected per-profile failure. |\r\n\r\n## Data source and limits\r\n\r\n- **Primary:** the public profile HTML, including X's server-rendered `GraphQLRequestStream.Completed` payload. This is the same page any logged-out visitor sees.\r\n- **Fallbacks:** guest GraphQL (`UserByScreenName` header, cursor-paged `UserTweets` ranked timeline, Highlights), the syndication timeline, and the public FxTwitter mirror. Guest `UserByScreenName` is merged before FxTwitter; FxTwitter is a third-party mirror and remains the last resort for post views or a missing header field.\r\n- **No credentials:** no cookies, login, API key or user proxy is required.\r\n- **Proxy:** the actor tries a direct connection first, then the Apify AUTO datacenter tier after a real block. A paid residential tier is hardcoded as a last resort and is never a schema default. A user-supplied `proxyConfiguration` overrides the built-in ladder.\r\n\r\n### Honest limits\r\n\r\n- **About panel:** login-only. The legacy `about` value remains in the `include` enum for saved-task compatibility and is labeled as unavailable; requesting it emits an `aboutUnavailable` status instead of failing the task.\r\n- **Full replies:** X does not serve the full reply tab to logged-out callers, and the guest reply timeline/search refuses most attempts. Replies remain best effort and report `repliesLimited` when empty; use `postUrls` for a focal post plus up to three server-rendered replies.\r\n- **Full media tab:** X's `/media` route is a login wall for guests and the guest `UserMedia` operation returns 404. Media mode filters the collected timelines instead; cursor pagination can now collect far more media posts than the old one-page path.\r\n- **Deep history:** the guest `UserTweets` bottom cursor pages a profile timeline back to X's ceiling of about 3,200 posts, and `maxPostsPerProfile` supports 1-1000. Guest search is not reliable, so older-row top-ups beyond the cursor are best effort.\r\n- **Highlights:** the old `userId` Highlights query pages the curated set through its bottom cursor, not a fixed 20-row page. Observed depths include 113 (second-opinion sample), 287 (NASA) and 1,097 (elonmusk), bounded by the account's Highlights set and `maxPostsPerProfile`. The live `screenName` query remains a one-page fallback.\r\n- **Syndication quota:** about 30 profile timeline lookups per IP per 15 minutes. The actor falls back to guest GraphQL and FxTwitter instead of retrying forever.\r\n- **`listedCount`:** best effort only. Syndication returns a placeholder `0` and that value is never published; only a search author snapshot carries the real count, and guest search itself is limited.\r\n- **`postedFrom`, `place`:** conditional. `postedFrom` is filled when the timeline row carries a top-level `source`; `place` is not exposed by current cookieless payloads and is omitted.\r\n- **Views:** recent posts get views from the status page; tweets from before X introduced view counts have no view count anywhere.\r\n- **4K video:** the 4K MP4 can be hundreds of megabytes. `media.url` prefers 1080p or lower; the 4K URL stays in `variants`.\r\n\r\n## FAQ\r\n\r\n**Do I need an X account or developer key?** No. The actor only reads public pages and needs no credential input.\r\n\r\n**Why are there fewer posts than `maxPostsPerProfile`?** The account may have fewer public posts in the reachable timeline, the filters may remove rows, or X may refuse the guest search top-up. The actor merges and cursor-pages every public source, then reports `searchLimited` with the achieved count only when a real source cap leaves the target unmet.\r\n\r\n**Why is a count missing instead of `0`?** A missing field means X did not expose it. The actor never substitutes a sentinel or an invented zero. A real zero (for example `quoteCount: 0`) is preserved.\r\n\r\n**Why did my account get a `skipped` row?** An account-level filter (`onlyVerified`, `verifiedType`, follower range, account age, website, bio keywords or `activeWithinDays`) excluded it. The message says which one.\r\n\r\n**Can I collect protected accounts?** Only their public header. Posts require approval from the account owner, which the actor does not attempt.\r\n\r\n**Is the data live?** Yes. Every run reads X at request time; schedule the actor for hourly, daily or weekly refreshes.\r\n\r\n**How do I use the dataset?** Use the run page, the Apify API, webhooks, or a no-code connector such as Zapier, Make or Google Sheets.\r\n\r\n**Is this affiliated with X?** No. It is an independent third-party actor that reads publicly available pages.\r\n\r\n## Development\r\n\r\nRun the offline tests from the actor folder:\r\n\r\n```bash\r\npython -m pytest tests/ -q\r\n```\r\n\r\nRun locally with the Apify CLI:\r\n\r\n```bash\r\napify run --purge --input-file input.json\r\n```\r\n",
    "actorDefinition": {
      "actorSpecification": 1,
      "name": "x-twitter-profile-and-post-engagement-extractor",
      "title": "X (Twitter) Profile & Post Engagement Extractor",
      "description": "Get any public X profile — bio, followers, verification, avatar, banner — plus latest, top and media posts with full engagement metrics. No login required.",
      "seoTitle": "X (Twitter) Profile & Post Engagement Extractor",
      "seoDescription": "Scrape any public X profile and its posts: bio, followers, verification, avatar, banner, latest and top posts with engagement metrics.",
      "version": "1.0",
      "buildTag": "latest",
      "dockerfile": "./Dockerfile",
      "readme": "# X (Twitter) Profile & Post Engagement Extractor\r\n\r\nCollect public X profiles and posts without login, cookies, developer keys or API tokens. The actor returns a profile row, post rows with engagement and media, and explicit status rows when X limits or hides something.\r\n\r\n## What it collects\r\n\r\n- **Profile header** from the public profile page: handle, display name, bio, bio links and emails, location, website, join date, follower/following/post/media/like counters, verification type, avatar, banner, public birth date, pinned post and professional label.\r\n- **Latest posts** from the profile's server-rendered originals timeline, strictly newest first, topped up through the cursor-paged timeline when the target is higher than the 5-9 server-rendered rows. A pinned post is marked `isPinned` but stays in its chronological position (and may exceed the cap by one).\r\n- **Top posts** ranked by total engagement. X exposes no public Top tab to logged-out callers, so the actor combines the guest search Top product with the profile timeline candidate pool and ranks the merged set locally; a pinned post is not promoted above its earned rank.\r\n- **Media posts** by filtering the collected timelines to photos, videos and GIFs. X does not serve a full media tab to logged-out callers. Filter-heavy runs page the timeline until the requested number of matching rows is found or the source ceiling is reached, so rare GIF matches are not missed by a fixed buffer.\r\n- **Replies and Highlights** as best-effort groups. Highlights are cursor-paged through the legacy guest query, so large curated sets return their full observed depth; replies remain limited. X sometimes refuses those guest timelines; the actor reports `repliesLimited` or `highlightsLimited` instead of pretending the account has none.\r\n- **Specific post URLs** through `postUrls`: the focal post plus up to three server-rendered replies per URL.\r\n- **Derived activity metrics** on profile rows: account age, followers/following ratio, posts per day, last post date, days since the last post, recent average likes/replies/reposts/views and a recent engagement percentage.\r\n- **Provenance and timestamps** on every row: `type`, `recordType`, `sourceUrl`, `collectedAt` and `scrapedAt`.\r\n\r\nThe actor never signs in, never asks for cookies and never uses a user-supplied token.\r\n\r\n## Input\r\n\r\n| Field | Type | Default | Description |\r\n|---|---|---|---|\r\n| `usernames` | array | – | Optional; supply this, `postUrls`, or both. Handles (`NASA`, `@NASA`), profile links (`https://x.com/NASA`), numeric ids (`11348282`, `id:11348282`) or `https://x.com/i/user/11348282`. |\r\n| `include` | multi-select | `[\"profileInfo\", \"latestPosts\"]` | `profileInfo`, `latestPosts`, `topPosts`, `media`, `replies`, `highlights`; legacy `about` is accepted but reported as unavailable. Groups are additive. |\r\n| `postUrls` | array | `[]` | Exact post URLs or ids; can run alone or alongside profiles. Each returns the focal post and up to 3 replies. |\r\n| `maxPostsPerProfile` | integer 1-1000 | `50` | Post target per account. The actor follows X's guest timeline bottom cursor (X's own ceiling is about 3,200 posts). When a target is still unmet, older posts are filled from search windows only when X serves them. |\r\n| `includeReplies` | boolean | `false` | Keep replies the account sent (best effort). |\r\n| `includeRetweets` | boolean | `false` | Keep reposts. |\r\n| `includeHighlights` | boolean | `false` | Also collect the Highlights tab. Cursor-paged up to `maxPostsPerProfile`; observed depths include 113, 287 and 1,097 rows on different accounts. |\r\n| `dateFrom`, `dateTo` | date picker | – | Client-side post date window. `dateTo` includes the whole day. |\r\n| `language` | text | – | Post language, e.g. `en`, `es`, `ja`. Locale forms such as `en-US` and `pt-BR` are normalized to `en`/`pt` in code, so old tasks keep working. |\r\n| `minimumLikes`, `minimumRetweets`, `minimumReplies`, `minimumViews`, `minimumQuotes` | integer ≥ 0 | – | Client-side engagement minimums. Posts missing the requested count are dropped when the minimum is set. |\r\n| `onlyWithMedia` | boolean | `false` | Keep only posts with photos, videos or GIFs. |\r\n| `mediaType` | select | `any` | `any`, `photos`, `videos`, `gifs`. |\r\n| `onlyVerified` | boolean | `false` | Skip accounts without a verification badge. |\r\n| `verifiedType` | select | `any` | `any`, `blue`, `business`, `government`. X and mirror labels are normalized in code. |\r\n| `minFollowers`, `maxFollowers` | integer ≥ 0 | `0` | Account-level follower range. `0` means any. |\r\n| `minAccountAgeDays` | integer ≥ 0 | `0` | Skip accounts younger than this. Accounts without a public join date are skipped when set. |\r\n| `withWebsiteOnly` | boolean | `false` | Skip accounts with no website in the profile or bio. |\r\n| `bioKeywords` | array | `[]` | Keep accounts whose name or bio contains every keyword (case-insensitive). |\r\n| `activeWithinDays` | integer ≥ 0 | `0` | Skip accounts whose newest public post is older than this. |\r\n| `enrichEngagement` | boolean | `true` | Refresh each collected post through its public status page for views, bookmarks and complete media. One request per post; turn off for the cheapest profile-only runs. |\r\n| `proxyConfiguration` | proxy | – | Optional override. The actor works without a proxy and tries the direct connection first. |\r\n\r\n### Examples\r\n\r\nProfile only (the daily prefill shape):\r\n\r\n```json\r\n{\r\n  \"usernames\": [\"NASA\"],\r\n  \"include\": [\"profileInfo\"],\r\n  \"maxPostsPerProfile\": 3,\r\n  \"enrichEngagement\": false\r\n}\r\n```\r\n\r\nProfile plus latest and top posts:\r\n\r\n```json\r\n{\r\n  \"usernames\": [\"NASA\", \"https://x.com/apify\"],\r\n  \"include\": [\"profileInfo\", \"latestPosts\", \"topPosts\"],\r\n  \"maxPostsPerProfile\": 100\r\n}\r\n```\r\n\r\nHigh-engagement photo posts from a date range:\r\n\r\n```json\r\n{\r\n  \"usernames\": [\"NASA\"],\r\n  \"include\": [\"latestPosts\"],\r\n  \"mediaType\": \"photos\",\r\n  \"minimumLikes\": 5000,\r\n  \"minimumViews\": 1000000,\r\n  \"dateFrom\": \"2026-01-01\",\r\n  \"dateTo\": \"2026-10-27\"\r\n}\r\n```\r\n\r\nLead-gen profile filter:\r\n\r\n```json\r\n{\r\n  \"usernames\": [\"apify\", \"NASA\"],\r\n  \"include\": [\"profileInfo\"],\r\n  \"minFollowers\": 10000,\r\n  \"minAccountAgeDays\": 365,\r\n  \"withWebsiteOnly\": true,\r\n  \"bioKeywords\": [\"space\"]\r\n}\r\n```\r\n\r\nSpecific posts:\r\n\r\n```json\r\n{\r\n  \"usernames\": [\"NASA\"],\r\n  \"include\": [\"profileInfo\"],\r\n  \"postUrls\": [\"https://x.com/NASA/status/2114801524358406570\"]\r\n}\r\n```\r\n\r\n## Output\r\n\r\nEvery row has `type` (`profile`, `post` or `status`), `recordType`, `collectedAt` and `scrapedAt`. Profile and post rows, and status rows with a known account or post URL, also carry `sourceUrl`. Empty fields are omitted recursively: the dataset never contains `null`, `\"\"`, `[]` or `{}`.\r\n\r\n### Profile row\r\n\r\nKeeps the original fields: `id`, `username`, `name`, `bio`, `bioLinks`, `bioEmails`, `location`, `website`, `joinedAt`, `birthDate`, `followers`, `following`, `postCount`, `mediaCount`, `likesGiven`, `listedCount`, `isBlueVerified`, `verified`, `verifiedType`, `label`, `isProtected`, `avatarUrl`, `bannerUrl`, `pinnedPostId`, `pinnedTweetId`, `pinnedTweet`, `mediaTweets`, `withReplies`, `profileStatus`, `collectedAt`.\r\n\r\nAdds: `profileUrl`, `websiteDisplayUrl`, `bioMentions`, `accountAgeDays`, `followersToFollowingRatio`, `postsPerDay`, `lastPostAt`, `daysSinceLastPost`, `recentAvgLikes`, `recentAvgReplies`, `recentAvgReposts`, `recentAvgViews`, `recentEngagementRatePct`, `professionalType`, `professionalCategory`, `affiliateLabel`, `highlightCount`, `recordType`, `sourceUrl`, `scrapedAt`.\r\n\r\n`websiteDisplayUrl` is the label X shows for the profile's website entity (for example `nasa.gov`). Bio links stay in `bioLinks` and are never reused as the website label.\r\n\r\nConditional fields are omitted when X does not expose them. `listedCount` is not guaranteed by current cookieless payloads.\r\n\r\n### Post row\r\n\r\nKeeps the original fields: `id`, `url`, `text`, `createdAt`, `language`, `replyCount`, `retweetCount`, `likeCount`, `quoteCount`, `bookmarkCount`, `viewCount`, `engagementRate`, `isReply`, `isRetweet`, `isQuote`, `isNoteTweet`, `isPinned`, `conversationId`, `threadRootId`, `threadPosition`, `inReplyToTweetId`, `inReplyToUserId`, `inReplyToUsername`, `hashtags`, `mentions`, `urls`, `cashtags`, `media`, `quotedTweet`, `quotedTweetId`, `poll`, `card`, `possiblySensitive`, `place`, `postedFrom`, `withheldInCountries`, `isEdited`, `editControl`, `newsActionType`, `parentTweet`, `author`, `username`, `collectedAt`.\r\n\r\nAdds: `totalEngagement`, `engagementPerView`, `mediaTypes`, `source`, `isHighlight`, `authorId`, `authorUsername`, `authorName`, `recordType`, `sourceUrl`, `scrapedAt`.\r\n\r\n`source` is provenance: `profileLatest`, `profileTop`, `profileHighlight`, `profileReplies`, `syndication`, `search` or `status`.\r\n\r\n`conversationId` and `threadRootId` are set on `postUrls` replies from the focal post when the status page omits them, so every reply can be grouped by conversation.\r\n\r\n`totalEngagement` is emitted only when likes, reposts, replies and quotes are all real numbers. `engagementPerView` additionally needs a positive view count. No count is invented.\r\n\r\n**Media item:** `type` (`photo` / `video` / `gif`), `url`, `previewImageUrl`, `width`, `height`, `durationSeconds`, `viewCount`, `altText`, `variants`, and where X provides them `mediaKey`, `allowDownload`, `mediaAvailability`, `focusRects`. Video `url` prefers the best MP4 at or below 1080p; the full variant list (including 4K) remains in `variants`.\r\n\r\n### Status row\r\n\r\nStatus rows explain a skipped, protected, missing, empty or limited account:\r\n\r\n| Status | Meaning |\r\n|---|---|\r\n| `noInput` | No usable usernames or post URLs were supplied. |\r\n| `invalidUsername` | A username/link/id could not be parsed. |\r\n| `notFound` | The account does not exist, was removed or is suspended. |\r\n| `rateLimited` | X temporarily limited the profile request. |\r\n| `skipped` | An account-level filter excluded the account. The message names the filter. |\r\n| `protected` | The account is protected; only its public header is returned. |\r\n| `repliesLimited` | Replies were requested but X served none or fewer than the target this run; `achieved` and `target` are included. |\r\n| `highlightsLimited` | Highlights were requested but X returned no rows or refused the request this run. |\r\n| `noMediaPosts` | Media mode found no matching photos, videos or GIFs. |\r\n| `noPosts` | Posts were collected but every one was filtered out. |\r\n| `noVisiblePosts` | The account exists but X shows no public posts. |\r\n| `timelineUnavailable` | The profile loaded but its posts could not be collected. |\r\n| `searchLimited` | Fewer posts than requested were available; `achieved` and `target` are included. |\r\n| `aboutUnavailable` | A legacy task requested X's login-only About panel. |\r\n| `error` | An unexpected per-profile failure. |\r\n\r\n## Data source and limits\r\n\r\n- **Primary:** the public profile HTML, including X's server-rendered `GraphQLRequestStream.Completed` payload. This is the same page any logged-out visitor sees.\r\n- **Fallbacks:** guest GraphQL (`UserByScreenName` header, cursor-paged `UserTweets` ranked timeline, Highlights), the syndication timeline, and the public FxTwitter mirror. Guest `UserByScreenName` is merged before FxTwitter; FxTwitter is a third-party mirror and remains the last resort for post views or a missing header field.\r\n- **No credentials:** no cookies, login, API key or user proxy is required.\r\n- **Proxy:** the actor tries a direct connection first, then the Apify AUTO datacenter tier after a real block. A paid residential tier is hardcoded as a last resort and is never a schema default. A user-supplied `proxyConfiguration` overrides the built-in ladder.\r\n\r\n### Honest limits\r\n\r\n- **About panel:** login-only. The legacy `about` value remains in the `include` enum for saved-task compatibility and is labeled as unavailable; requesting it emits an `aboutUnavailable` status instead of failing the task.\r\n- **Full replies:** X does not serve the full reply tab to logged-out callers, and the guest reply timeline/search refuses most attempts. Replies remain best effort and report `repliesLimited` when empty; use `postUrls` for a focal post plus up to three server-rendered replies.\r\n- **Full media tab:** X's `/media` route is a login wall for guests and the guest `UserMedia` operation returns 404. Media mode filters the collected timelines instead; cursor pagination can now collect far more media posts than the old one-page path.\r\n- **Deep history:** the guest `UserTweets` bottom cursor pages a profile timeline back to X's ceiling of about 3,200 posts, and `maxPostsPerProfile` supports 1-1000. Guest search is not reliable, so older-row top-ups beyond the cursor are best effort.\r\n- **Highlights:** the old `userId` Highlights query pages the curated set through its bottom cursor, not a fixed 20-row page. Observed depths include 113 (second-opinion sample), 287 (NASA) and 1,097 (elonmusk), bounded by the account's Highlights set and `maxPostsPerProfile`. The live `screenName` query remains a one-page fallback.\r\n- **Syndication quota:** about 30 profile timeline lookups per IP per 15 minutes. The actor falls back to guest GraphQL and FxTwitter instead of retrying forever.\r\n- **`listedCount`:** best effort only. Syndication returns a placeholder `0` and that value is never published; only a search author snapshot carries the real count, and guest search itself is limited.\r\n- **`postedFrom`, `place`:** conditional. `postedFrom` is filled when the timeline row carries a top-level `source`; `place` is not exposed by current cookieless payloads and is omitted.\r\n- **Views:** recent posts get views from the status page; tweets from before X introduced view counts have no view count anywhere.\r\n- **4K video:** the 4K MP4 can be hundreds of megabytes. `media.url` prefers 1080p or lower; the 4K URL stays in `variants`.\r\n\r\n## FAQ\r\n\r\n**Do I need an X account or developer key?** No. The actor only reads public pages and needs no credential input.\r\n\r\n**Why are there fewer posts than `maxPostsPerProfile`?** The account may have fewer public posts in the reachable timeline, the filters may remove rows, or X may refuse the guest search top-up. The actor merges and cursor-pages every public source, then reports `searchLimited` with the achieved count only when a real source cap leaves the target unmet.\r\n\r\n**Why is a count missing instead of `0`?** A missing field means X did not expose it. The actor never substitutes a sentinel or an invented zero. A real zero (for example `quoteCount: 0`) is preserved.\r\n\r\n**Why did my account get a `skipped` row?** An account-level filter (`onlyVerified`, `verifiedType`, follower range, account age, website, bio keywords or `activeWithinDays`) excluded it. The message says which one.\r\n\r\n**Can I collect protected accounts?** Only their public header. Posts require approval from the account owner, which the actor does not attempt.\r\n\r\n**Is the data live?** Yes. Every run reads X at request time; schedule the actor for hourly, daily or weekly refreshes.\r\n\r\n**How do I use the dataset?** Use the run page, the Apify API, webhooks, or a no-code connector such as Zapier, Make or Google Sheets.\r\n\r\n**Is this affiliated with X?** No. It is an independent third-party actor that reads publicly available pages.\r\n\r\n## Development\r\n\r\nRun the offline tests from the actor folder:\r\n\r\n```bash\r\npython -m pytest tests/ -q\r\n```\r\n\r\nRun locally with the Apify CLI:\r\n\r\n```bash\r\napify run --purge --input-file input.json\r\n```\r\n",
      "input": {
        "title": "X (Twitter) Profile & Post Engagement Extractor",
        "description": "Collect any public X profile and its posts without login, cookies or API keys.",
        "type": "object",
        "schemaVersion": 1,
        "properties": {
          "usernames": {
            "title": "X usernames, profile links or numeric ids",
            "type": "array",
            "description": "Accounts to collect. Paste handles like `elonmusk`, `@apify`, profile links like `https://x.com/elonmusk`, numeric ids like `11348282` or `id:11348282`. Handles, links and ids can be mixed.",
            "editor": "stringList",
            "default": [],
            "prefill": [
              "NASA"
            ],
            "example": [
              "NASA",
              "https://x.com/apify",
              "id:11348282"
            ]
          },
          "include": {
            "title": "What to collect",
            "type": "array",
            "description": "Pick one or more data groups. Everything works without cookies. `topPosts` is engagement-ranked from the candidate pool (X serves no public Top tab to guests). `replies` and `highlights` are best-effort: X sometimes refuses those guest timelines.",
            "editor": "select",
            "items": {
              "type": "string",
              "enum": [
                "profileInfo",
                "latestPosts",
                "topPosts",
                "media",
                "replies",
                "highlights",
                "about"
              ],
              "enumTitles": [
                "Profile info (bio, followers, verification)",
                "Latest posts (newest first)",
                "Top posts (engagement-ranked)",
                "Media posts only (photos, videos, GIFs)",
                "Replies the account sent (best effort)",
                "Highlights tab (cursor-paged)",
                "About panel (login-only; reported as unavailable)"
              ]
            },
            "uniqueItems": true,
            "default": [
              "profileInfo",
              "latestPosts"
            ],
            "prefill": [
              "profileInfo"
            ]
          },
          "postUrls": {
            "title": "Specific post URLs or ids",
            "type": "array",
            "description": "Optional. Collect these exact posts (focal post plus up to 3 replies each) in addition to the profiles above. Accepts `https://x.com/user/status/123` or a numeric post id.",
            "editor": "stringList",
            "default": [],
            "example": [
              "https://x.com/NASA/status/2114801524358406570"
            ]
          },
          "maxPostsPerProfile": {
            "title": "Maximum posts per profile",
            "type": "integer",
            "description": "Target number of posts per account (1-1000). The actor follows X's guest timeline bottom cursor back to X's ceiling of about 3,200 posts; guest search top-ups are best effort. Filter-heavy runs page until the target number of matching rows is found or the source ceiling is reached. A pinned post can be added beyond this cap when it passes your filters; it stays in chronological order for latest and in ranked order for top.",
            "default": 50,
            "prefill": 3,
            "minimum": 1,
            "maximum": 1000
          },
          "includeReplies": {
            "title": "Include replies",
            "type": "boolean",
            "description": "Keep replies the account sent. Best effort: X only serves the reply timeline to logged-out callers on some attempts.",
            "default": false,
            "prefill": false
          },
          "includeRetweets": {
            "title": "Include reposts",
            "type": "boolean",
            "description": "Keep reposts in the results. Turn off to get only posts the account wrote itself.",
            "default": false,
            "prefill": false
          },
          "includeHighlights": {
            "title": "Include Highlights tab",
            "type": "boolean",
            "description": "Also collect the account's curated Highlights tab. The guest legacy userId query pages the whole set through its bottom cursor (observed 100-1,097 rows on large accounts); the live one-page query remains a fallback.",
            "default": false,
            "prefill": false
          },
          "dateFrom": {
            "title": "Posted after",
            "type": "string",
            "description": "Optional. Only keep posts published on or after this date.",
            "editor": "datepicker"
          },
          "dateTo": {
            "title": "Posted before",
            "type": "string",
            "description": "Optional. Only keep posts published on or before this date.",
            "editor": "datepicker"
          },
          "language": {
            "title": "Post language",
            "type": "string",
            "description": "Optional. Keep only posts detected in this language, e.g. `en`, `es`, `ja`. Locale forms such as `en-US` or `pt-BR` are normalized to their two-letter code in code, so old tasks keep working.",
            "editor": "textfield"
          },
          "minimumLikes": {
            "title": "Minimum likes",
            "type": "integer",
            "description": "Optional. Drop posts with fewer likes than this.",
            "minimum": 0,
            "maximum": 1000000000
          },
          "minimumRetweets": {
            "title": "Minimum reposts",
            "type": "integer",
            "description": "Optional. Drop posts with fewer reposts than this.",
            "minimum": 0,
            "maximum": 1000000000
          },
          "minimumReplies": {
            "title": "Minimum replies",
            "type": "integer",
            "description": "Optional. Drop posts with fewer replies than this.",
            "minimum": 0,
            "maximum": 1000000000
          },
          "minimumViews": {
            "title": "Minimum views",
            "type": "integer",
            "description": "Optional. Drop posts with fewer views than this. Posts whose view count X does not expose are dropped when this is set.",
            "minimum": 0,
            "maximum": 1000000000000
          },
          "minimumQuotes": {
            "title": "Minimum quotes",
            "type": "integer",
            "description": "Optional. Drop posts with fewer quotes than this. Posts whose quote count X does not expose are dropped when this is set.",
            "minimum": 0,
            "maximum": 1000000000
          },
          "onlyWithMedia": {
            "title": "Only posts with images, videos or GIFs",
            "type": "boolean",
            "description": "Keep only posts that contain media.",
            "default": false,
            "prefill": false
          },
          "mediaType": {
            "title": "Media type",
            "type": "string",
            "description": "Optional. Keep only posts with this media type. `media` in What to collect already limits rows to media posts.",
            "editor": "select",
            "enum": [
              "any",
              "photos",
              "videos",
              "gifs"
            ],
            "enumTitles": [
              "Any media",
              "Photos only",
              "Videos only",
              "GIFs only"
            ],
            "default": "any",
            "prefill": "any"
          },
          "onlyVerified": {
            "title": "Only verified accounts",
            "type": "boolean",
            "description": "Skip accounts that do not carry a verified badge.",
            "default": false,
            "prefill": false
          },
          "verifiedType": {
            "title": "Verification type",
            "type": "string",
            "description": "Optional. Keep only accounts with this verification type. X and FxTwitter labels are normalized in code.",
            "editor": "select",
            "enum": [
              "any",
              "blue",
              "business",
              "government"
            ],
            "enumTitles": [
              "Any verification",
              "Blue / individual",
              "Business / organization",
              "Government"
            ],
            "default": "any",
            "prefill": "any"
          },
          "minFollowers": {
            "title": "Minimum followers",
            "type": "integer",
            "description": "Optional. Skip accounts below this follower count. 0 means any.",
            "default": 0,
            "prefill": 0,
            "minimum": 0,
            "maximum": 1000000000
          },
          "maxFollowers": {
            "title": "Maximum followers",
            "type": "integer",
            "description": "Optional. Skip accounts above this follower count. 0 means any.",
            "default": 0,
            "prefill": 0,
            "minimum": 0,
            "maximum": 1000000000
          },
          "minAccountAgeDays": {
            "title": "Minimum account age (days)",
            "type": "integer",
            "description": "Optional. Skip accounts younger than this. 0 means any. Accounts without a public join date are skipped when this is set.",
            "default": 0,
            "prefill": 0,
            "minimum": 0,
            "maximum": 100000
          },
          "withWebsiteOnly": {
            "title": "Only accounts with a website",
            "type": "boolean",
            "description": "Skip accounts that expose no website link in their profile or bio.",
            "default": false,
            "prefill": false
          },
          "bioKeywords": {
            "title": "Bio keywords",
            "type": "array",
            "description": "Optional. Keep only accounts whose name or bio contains every keyword (case-insensitive).",
            "editor": "stringList",
            "default": []
          },
          "activeWithinDays": {
            "title": "Active within days",
            "type": "integer",
            "description": "Optional. Skip accounts whose newest public post is older than this many days. 0 means any. Requires reading the latest posts to evaluate.",
            "default": 0,
            "prefill": 0,
            "minimum": 0,
            "maximum": 36500
          },
          "enrichEngagement": {
            "title": "Enrich engagement per post",
            "type": "boolean",
            "description": "Refresh each collected post through its public status page for views, bookmarks and complete media. Adds one request per post. Turn off for the cheapest profile-only runs.",
            "default": true,
            "prefill": false
          },
          "proxyConfiguration": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "Optional. The actor works without a proxy and tries a direct connection first. Use this only if you want to override the built-in direct -> Apify AUTO escalation.",
            "editor": "proxy",
            "prefill": {
              "useApifyProxy": false
            },
            "sectionCaption": "Advanced",
            "sectionDescription": "Optional extras. The actor works fully without these."
          }
        },
        "required": []
      },
      "output": {
        "actorOutputSchemaVersion": 1,
        "title": "X (Twitter) Profile & Post Engagement Extractor Output",
        "description": "One profile row per account, one post row per collected post (cursor-paged up to maxPostsPerProfile), and status rows for protected, missing, empty, filtered or limited accounts. Every row carries type, recordType, sourceUrl, collectedAt and scrapedAt.",
        "type": "object",
        "properties": {
          "profiles": {
            "type": "string",
            "title": "Profiles, posts and statuses",
            "description": "Open the default dataset: profile rows (bio, counters, verification, pinned post), post rows (engagement, media, provenance) and explicit status rows.",
            "template": "{{links.apiDefaultDatasetUrl}}/items"
          }
        }
      },
      "categories": [
        "SOCIAL_MEDIA"
      ],
      "storages": {
        "dataset": {
          "actorSpecification": 1,
          "views": {
            "overview": {
              "title": "Profiles, posts and statuses",
              "transformation": {
                "fields": [
                  "type",
                  "recordType",
                  "status",
                  "message",
                  "username",
                  "name",
                  "text",
                  "createdAt",
                  "source",
                  "likeCount",
                  "retweetCount",
                  "replyCount",
                  "quoteCount",
                  "viewCount",
                  "bookmarkCount",
                  "totalEngagement",
                  "engagementRate",
                  "mediaTypes",
                  "isPinned",
                  "isHighlight",
                  "url",
                  "sourceUrl",
                  "profileUrl",
                  "followers",
                  "following",
                  "postCount",
                  "verifiedType",
                  "collectedAt",
                  "scrapedAt"
                ]
              },
              "display": {
                "component": "table",
                "properties": {}
              }
            }
          }
        }
      },
      "environmentVariables": {}
    },
    "buildNumber": "1.0.2",
    "imageDigest": "5a0740101e6bd6703d42cb17f85c4e786255db68b4d37450c23d3ab5ff1d8ff9",
    "actVersion": {
      "sourceType": "SOURCE_FILES",
      "buildTag": "latest",
      "versionNumber": "1.0"
    },
    "usage": {
      "ACTOR_COMPUTE_UNITS": 0.012477777777777779
    }
  }
}