{
  "data": {
    "id": "b2lfr5bXEkHVsmX4j",
    "actId": "fnHBLqMkGad5UusrY",
    "userId": "zq8lKbpLzTjGDayPC",
    "startedAt": "2026-10-29T07:50:45.294Z",
    "finishedAt": "2026-10-29T07:51:01.103Z",
    "status": "SUCCEEDED",
    "meta": {
      "origin": "CLI"
    },
    "stats": {
      "durationMillis": 15738,
      "runTimeSecs": 15.738,
      "computeUnits": 0.017486666666666668,
      "imageSizeBytes": 805804032
    },
    "options": {
      "useCache": true,
      "betaPackages": false,
      "memoryMbytes": 4096,
      "diskMbytes": 8192
    },
    "createdByOrganizationMemberUserId": "GK9YjZRyQZVzOdoH5",
    "exitCode": 0,
    "inputSchema": "{\n  \"title\": \"Twitter (X) Media Downloader\",\n  \"type\": \"object\",\n  \"schemaVersion\": 1,\n  \"properties\": {\n    \"postUrls\": {\n      \"title\": \"Post links or IDs\",\n      \"type\": \"array\",\n      \"description\": \"Paste links of posts to download media from. Accepts x.com and twitter.com links, i/web/status links, or bare post IDs. Optional if you provide usernames or profile links below.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"prefill\": [\n        \"https://x.com/NASA/status/2107095768821576019\"\n      ],\n      \"example\": [\n        \"https://x.com/NASA/status/2107095768821576019\",\n        \"2107516524965794217\"\n      ]\n    },\n    \"usernames\": {\n      \"title\": \"Usernames or profile links to scan\",\n      \"type\": \"array\",\n      \"description\": \"Scan a profile's media posts and download their photos, videos and GIFs. Type handles with or without @, or paste full profile links. Optional if you provide post links above.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"prefill\": [],\n      \"example\": [\n        \"NASA\",\n        \"https://x.com/elonmusk\"\n      ]\n    },\n    \"maxPostsPerUser\": {\n      \"title\": \"Maximum media posts to check per username\",\n      \"type\": \"integer\",\n      \"description\": \"How many of each profile's most recent media posts to look at. Posts without matching media or filtered-out posts are skipped.\",\n      \"default\": 20,\n      \"prefill\": 1,\n      \"minimum\": 1,\n      \"maximum\": 1000\n    },\n    \"maxItems\": {\n      \"title\": \"Maximum media files to collect\",\n      \"type\": \"integer\",\n      \"description\": \"Hard cap on dataset rows for the whole run. The actor stops collecting and downloading once this many files are emitted.\",\n      \"default\": 100,\n      \"prefill\": 1,\n      \"minimum\": 1,\n      \"maximum\": 10000\n    },\n    \"mediaTypes\": {\n      \"title\": \"Media types to download\",\n      \"type\": \"array\",\n      \"description\": \"Choose which kinds of media to download. All types are selected by default.\",\n      \"editor\": \"select\",\n      \"items\": {\n        \"type\": \"string\",\n        \"enum\": [\n          \"video\",\n          \"photo\",\n          \"gif\"\n        ],\n        \"enumTitles\": [\n          \"Videos\",\n          \"Photos\",\n          \"GIFs\"\n        ]\n      },\n      \"default\": [\n        \"video\",\n        \"photo\",\n        \"gif\"\n      ],\n      \"prefill\": [\n        \"video\",\n        \"photo\",\n        \"gif\"\n      ]\n    },\n    \"imageQuality\": {\n      \"title\": \"Photo quality\",\n      \"type\": \"string\",\n      \"description\": \"Quality to use for photos (and video poster images). Original is the full-resolution file posted on X.\",\n      \"editor\": \"select\",\n      \"default\": \"orig\",\n      \"enum\": [\n        \"orig\",\n        \"large\",\n        \"medium\",\n        \"small\"\n      ],\n      \"enumTitles\": [\n        \"Original (highest quality)\",\n        \"Large\",\n        \"Medium\",\n        \"Small (lightest)\"\n      ],\n      \"prefill\": \"orig\"\n    },\n    \"videoQuality\": {\n      \"title\": \"Video quality\",\n      \"type\": \"string\",\n      \"description\": \"Preferred video resolution. The actor picks the closest available quality when the exact one is not offered.\",\n      \"editor\": \"select\",\n      \"default\": \"best\",\n      \"enum\": [\n        \"best\",\n        \"2160p\",\n        \"1080p\",\n        \"720p\",\n        \"480p\",\n        \"worst\"\n      ],\n      \"enumTitles\": [\n        \"Best available\",\n        \"2160p (4K)\",\n        \"1080p (Full HD)\",\n        \"720p (HD)\",\n        \"480p (SD)\",\n        \"Worst (smallest file)\"\n      ],\n      \"prefill\": \"best\"\n    },\n    \"downloadAllQualities\": {\n      \"title\": \"Download every video quality\",\n      \"type\": \"boolean\",\n      \"description\": \"Emit and download one separate file for every progressive MP4 quality X offers (for example 1080p, 720p, 480p) instead of only the preferred one. Every quality gets its own row, filename and download link.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"includePosterImage\": {\n      \"title\": \"Also save video poster images\",\n      \"type\": \"boolean\",\n      \"description\": \"Save the thumbnail/poster image of each video or GIF as an additional photo file.\",\n      \"default\": true,\n      \"prefill\": false\n    },\n    \"includeAltText\": {\n      \"title\": \"Include image alt text\",\n      \"type\": \"boolean\",\n      \"description\": \"Attach the alt text (image description) X stores for each photo or video thumbnail to the media row.\",\n      \"default\": true,\n      \"prefill\": true\n    },\n    \"audioOnly\": {\n      \"title\": \"Audio only (extract sound from videos)\",\n      \"type\": \"boolean\",\n      \"description\": \"Download only the audio track of videos. Photos and GIFs are skipped. Files are extracted with ffmpeg and file saving is enabled automatically.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"audioFormat\": {\n      \"title\": \"Audio file format\",\n      \"type\": \"string\",\n      \"description\": \"Format used when Audio only is on.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"m4a\",\n        \"mp3\"\n      ],\n      \"enumTitles\": [\n        \"M4A (AAC, smaller, native)\",\n        \"MP3 (widest compatibility)\"\n      ],\n      \"default\": \"m4a\",\n      \"prefill\": \"m4a\"\n    },\n    \"includeQuotedMedia\": {\n      \"title\": \"Include media from quoted posts\",\n      \"type\": \"boolean\",\n      \"description\": \"Also download the photos, videos and GIFs that appear inside a quoted post (a post embedded in the linked post). Those rows are marked with mediaSource \\\"quoted post\\\".\",\n      \"default\": true,\n      \"prefill\": true\n    },\n    \"includeMetadata\": {\n      \"title\": \"Include post and author metadata\",\n      \"type\": \"boolean\",\n      \"description\": \"Attach the post text, date, conversation and reply/quote flags, hashtags, mentions, links, like/repost/reply/quote/bookmark/view counts, author name, avatar, verification and follower count, and a derived engagement rate to every downloaded file.\",\n      \"default\": true,\n      \"prefill\": true\n    },\n    \"minLikes\": {\n      \"title\": \"Minimum likes (profile scans)\",\n      \"type\": \"integer\",\n      \"description\": \"When scanning usernames, skip posts with fewer likes than this. Posts whose like count is unknown are kept. Leave at 0 to keep every post.\",\n      \"default\": 0,\n      \"prefill\": 0,\n      \"minimum\": 0,\n      \"maximum\": 1000000000,\n      \"sectionCaption\": \"Profile filters\",\n      \"sectionDescription\": \"Optional filters applied while scanning usernames. Directly linked posts are always collected.\"\n    },\n    \"minRetweets\": {\n      \"title\": \"Minimum reposts (profile scans)\",\n      \"type\": \"integer\",\n      \"description\": \"When scanning usernames, skip posts with fewer reposts than this. Posts whose repost count is unknown are kept. Leave at 0 to keep every post.\",\n      \"default\": 0,\n      \"prefill\": 0,\n      \"minimum\": 0,\n      \"maximum\": 1000000000\n    },\n    \"onlyVerifiedAuthors\": {\n      \"title\": \"Only verified authors (profile scans)\",\n      \"type\": \"boolean\",\n      \"description\": \"When scanning usernames, skip posts whose author is not verified. Posts where verification is unknown are kept.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"saveToFile\": {\n      \"title\": \"Save files and return download links\",\n      \"type\": \"boolean\",\n      \"description\": \"Download each file into this run's key-value store and add a public signed download link. Turn off to collect metadata and direct CDN links only.\",\n      \"default\": true,\n      \"prefill\": false\n    }\n  },\n  \"required\": []\n}",
    "readme": "# Twitter (X) Media Downloader\n\nCollect public Twitter / X media without an account, cookies or a browser. Paste post links or ask for a profile's media posts and get the actual files back — videos in any available quality, photos, GIFs, audio-only tracks and video posters — each with a public download link.\n\n## What it does\n\n- **Post mode** — one link or ID per post, including `x.com`, `twitter.com`, `mobile.twitter.com`, `/i/web/status/` links, `/status/<id>/video/1` links and bare numeric IDs.\n- **Profile mode** — walks a profile's media posts page by page, not just the handful shown to logged-out visitors.\n- **Both modes in one run** — results are de-duplicated across the two paths.\n- **Status rows** — deleted, protected, media-less and rate-limited targets produce an honest status row instead of a silent empty run.\n\n## Services\n\n| Service | What you get |\n|---|---|\n| Video download | Best, 2160p, 1080p, 720p, 480p or smallest MP4; every quality can be saved as separate files |\n| Photo download | Original, large, medium or small image files |\n| GIF download | The MP4 file X serves for animated GIFs |\n| Audio extraction | M4A (AAC) or MP3 soundtrack of a video |\n| Poster images | The video/GIF thumbnail as a separate photo file |\n| Quoted-post media | Media inside a quoted post, marked `mediaSource: \"quoted post\"` |\n| Author and post context | Text, date, engagement counts, author name/avatar/verification/followers, hashtags, mentions, links, reply/quote flags and quoted post IDs |\n| Profile context | Profile ID, follower count and media count on rows found through a username |\n| Profile filters | Minimum likes, minimum reposts and verified-authors-only |\n\n## How it works\n\nThe actor reads X's public post data and the FxTwitter mirror, combines the fields each source is best at, and falls back to the X status page and oEmbed when a post looks missing. Videos and GIFs use X's direct CDN; if a direct file cannot be fetched, yt-dlp is tried as a last resort. Files are streamed to disk, checked for a sane size, stored in the run's key-value store and linked with a signed public URL. No request uses your cookies, login or API key.\n\n## Input\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `postUrls` | array | – | Post links or bare post IDs (the prefill uses one NASA post) |\n| `usernames` | array | – | Handles, `@handles` or profile links to scan |\n| `maxPostsPerUser` | integer | `20` | Media posts to check per profile (1–1000) |\n| `maxItems` | integer | `100` | Hard cap on media rows for the whole run (1–10000) |\n| `mediaTypes` | array | `[\"video\", \"photo\", \"gif\"]` | `video`, `photo`, `gif` |\n| `imageQuality` | string | `orig` | `orig`, `large`, `medium`, `small` |\n| `videoQuality` | string | `best` | `best`, `2160p`, `1080p`, `720p`, `480p`, `worst` |\n| `downloadAllQualities` | boolean | `false` | One row and file per available MP4 quality |\n| `includePosterImage` | boolean | `true` | Save each video/GIF poster as a photo |\n| `includeAltText` | boolean | `true` | Attach X's image description when present |\n| `audioOnly` | boolean | `false` | Save only the audio track of videos; photos and GIFs are skipped |\n| `audioFormat` | string | `m4a` | `m4a` or `mp3` when audio-only is on |\n| `includeQuotedMedia` | boolean | `true` | Include media from a quoted post |\n| `includeMetadata` | boolean | `true` | Attach post, author and engagement context |\n| `minLikes` | integer | `0` | Profile scans only: skip posts below this like count |\n| `minRetweets` | integer | `0` | Profile scans only: skip posts below this repost count |\n| `onlyVerifiedAuthors` | boolean | `false` | Profile scans only: skip unverified authors |\n| `saveToFile` | boolean | `true` | Store files and return signed links; off means metadata and CDN links only |\n\nAt least one post link/ID or username is required. Directly linked posts are always collected; the three filters apply while scanning usernames.\n\n### Example: download two posts\n\n```json\n{\n  \"postUrls\": [\n    \"https://x.com/NASA/status/2107095768821576019\",\n    \"https://x.com/SheCodeAfrica/status/2107516524965794217\"\n  ],\n  \"videoQuality\": \"1080p\",\n  \"maxItems\": 10\n}\n```\n\n### Example: scan a profile's media\n\n```json\n{\n  \"usernames\": [\"NASA\"],\n  \"maxPostsPerUser\": 50,\n  \"maxItems\": 100,\n  \"mediaTypes\": [\"video\", \"photo\"],\n  \"minLikes\": 100,\n  \"onlyVerifiedAuthors\": true\n}\n```\n\n### Example: audio-only MP3\n\n```json\n{\n  \"postUrls\": [\"https://x.com/NASA/status/2107095768821576019\"],\n  \"audioOnly\": true,\n  \"audioFormat\": \"mp3\"\n}\n```\n\n## Output\n\nEvery dataset row has `type`: `media file` for a collectible file, or `status` for a run message.\n\n| Group | Fields |\n|---|---|\n| Identity | `tweetId`, `tweetUrl`, `postedAt`, `conversationId`, `inReplyToTweetId`, `isReply`, `isQuote`, `isRetweet`, `quotedTweetId`, `quotedTweetUrl` |\n| Media | `mediaType`, `mediaUrl`, `mediaUrlOriginal`, `contentType`, `mediaId`, `mediaKey`, `thumbnailUrl`, `posterImageUrl`, `filename`, `fileSizeBytes`, `width`, `height`, `durationSeconds`, `focusRects`, `allowDownload` |\n| Download | `downloadUrl`, `downloadStatus`, `message` |\n| Video quality | `variantQuality`, `variantBitrate`, `availableQualities`, `qualityCount`, `isBestQuality` |\n| Audio | `audioFormat` |\n| Author | `uploaderUsername`, `uploaderName`, `uploaderVerified`, `uploaderAvatarUrl`, `uploaderFollowers` |\n| Post context | `tweetText`, `likeCount`, `retweetCount`, `replyCount`, `quoteCount`, `bookmarkCount`, `viewCount`, `engagementRate`, `hashtags`, `mentions`, `urls` |\n| Source | `mediaSource` (`post`, `quoted post` or `poster image`) |\n| Profile | `profileId`, `profileFollowers`, `profileMediaCount` |\n| Run | `collectedAt` |\n\nEmpty fields are omitted. Status rows carry `status` (`noInput`, `invalidLink`, `invalidUsername`, `notFound`, `rateLimited`, `error`, `noMatchingMedia`, `noRecords`), a human-readable `message`, and the related `tweetUrl`, `tweetId` or `username`.\n\n### Example record\n\n```json\n{\n  \"type\": \"media file\",\n  \"tweetId\": \"2107095768821576019\",\n  \"tweetUrl\": \"https://x.com/i/status/2107095768821576019\",\n  \"postedAt\": \"2026-10-05T13:09:13+00:00\",\n  \"mediaType\": \"video\",\n  \"mediaUrl\": \"https://video.twimg.com/amplify_video/.../avc1/1920x1080/video.mp4?tag=16\",\n  \"contentType\": \"video/mp4\",\n  \"mediaId\": \"2107095744146460672\",\n  \"filename\": \"2107095768821576019_video_1_1080p.mp4\",\n  \"fileSizeBytes\": 3178943,\n  \"width\": 1920,\n  \"height\": 1080,\n  \"durationSeconds\": 13.3,\n  \"downloadUrl\": \"https://api.apify.com/v2/key-value-stores/{STORE_ID}/records/...?signature=...\",\n  \"downloadStatus\": \"finished\",\n  \"uploaderUsername\": \"NASA\",\n  \"uploaderName\": \"NASA\",\n  \"uploaderVerified\": true,\n  \"uploaderFollowers\": 92310189,\n  \"likeCount\": 7476,\n  \"retweetCount\": 1125,\n  \"quoteCount\": 97,\n  \"bookmarkCount\": 332,\n  \"viewCount\": 2961506,\n  \"variantQuality\": \"1080p\",\n  \"isBestQuality\": true,\n  \"mediaSource\": \"post\",\n  \"collectedAt\": \"2026-10-29T00:00:00+00:00\"\n}\n```\n\n## Usage cost\n\nYou pay the actor's per-result price shown on its Apify page plus normal Apify platform usage. With `saveToFile: false` no media bytes are stored, so a metadata-only run is the cheapest option. Downloads add key-value store usage proportional to the real file sizes. Keep `maxItems` and `maxPostsPerUser` at the values you need — the actor stops as soon as `maxItems` is reached.\n\n## FAQ\n\n**Do I need an X account, cookies or an API key?** No. Everything is read from public, logged-out sources.\n\n**Can I download from a private or protected account?** No. Protected and deleted posts return a `notFound` status row; they are not silently skipped.\n\n**How far back does a profile scan go?** The walk follows X's media timeline page by page until `maxPostsPerUser`, `maxItems` or the end of the timeline. Large profiles can be rate-limited; lower `maxPostsPerUser` and re-run later.\n\n**How are video qualities chosen?** The closest available quality to your preference is used. Portrait videos are compared by their short side, so a 1080×1920 video counts as 1080p. With `downloadAllQualities`, every progressive MP4 becomes its own row and file.\n\n**What happens with quoted posts?** Media inside a quoted post is included by default and marked `mediaSource: \"quoted post\"`. Turn `includeQuotedMedia` off to keep only the linked post's own media.\n\n**Does audio-only work on GIFs?** No. Animated GIFs have no audio track, so they are skipped in audio-only mode along with photos.\n\n**What if a post has no media?** You get one `noMatchingMedia` status row explaining it, not an empty dataset.\n\n**Do download links expire?** Signed key-value store links follow your Apify storage retention. Re-run the actor or copy the files to your own storage for permanent archiving.\n\n**How do the profile filters treat unknown counts?** A post whose like/repost count or verification status is unknown is kept rather than excluded.\n\n**Can I still use my saved inputs from the previous version?** Yes. Old input keys are mapped automatically in code, so saved tasks keep running.\n\n## Notes\n\n- All timestamps are UTC ISO 8601.\n- Engagement rate is derived from likes, reposts, replies and quotes divided by the author's follower count; it is omitted when the follower count is unknown.\n- Media URLs are direct X CDN links and open without a Referer header.\n- Download links are public signed links tied to the run's key-value store.\n- Use the actor only for content you have the right to download.\n\n## Limitations\n\n- Public posts only: deleted posts, protected accounts and login-gated content are not accessible.\n- Profile scans cover the most recent media posts X exposes publicly, up to 1,000 posts per run; deeper history is not addressable and very large profiles can hit rate limits, so re-run later if a scan stops early.\n- A single media file larger than 1 GB is rejected to keep memory bounded.\n- Some media is marked by X as not downloadable; those rows report `downloadStatus: \"notDownloaded\"` with an explanation.\n- Quote media depends on the quoted post still being public.\n- X changes its public endpoints periodically; the actor keeps several fallbacks, but a run can still return `rateLimited` or `error` status rows during an outage.\n",
    "actorDefinition": {
      "actorSpecification": 1,
      "name": "twitter-x-media-downloader",
      "title": "Twitter (X) Media Downloader",
      "description": "Download videos, photos, GIFs, audio and poster images from public X posts. Use post links or scan profile media, pick any video quality, and get every file with a public download link. No login or cookies.",
      "seoTitle": "Twitter Video Downloader - X Videos, Photos, GIFs & Audio",
      "seoDescription": "Download videos, photos, GIFs and audio from public X (Twitter) posts without login. Browse profile media, choose any video quality and get public download links.",
      "version": "1.0",
      "buildTag": "latest",
      "meta": {
        "templateId": "python-start"
      },
      "readme": "# Twitter (X) Media Downloader\n\nCollect public Twitter / X media without an account, cookies or a browser. Paste post links or ask for a profile's media posts and get the actual files back — videos in any available quality, photos, GIFs, audio-only tracks and video posters — each with a public download link.\n\n## What it does\n\n- **Post mode** — one link or ID per post, including `x.com`, `twitter.com`, `mobile.twitter.com`, `/i/web/status/` links, `/status/<id>/video/1` links and bare numeric IDs.\n- **Profile mode** — walks a profile's media posts page by page, not just the handful shown to logged-out visitors.\n- **Both modes in one run** — results are de-duplicated across the two paths.\n- **Status rows** — deleted, protected, media-less and rate-limited targets produce an honest status row instead of a silent empty run.\n\n## Services\n\n| Service | What you get |\n|---|---|\n| Video download | Best, 2160p, 1080p, 720p, 480p or smallest MP4; every quality can be saved as separate files |\n| Photo download | Original, large, medium or small image files |\n| GIF download | The MP4 file X serves for animated GIFs |\n| Audio extraction | M4A (AAC) or MP3 soundtrack of a video |\n| Poster images | The video/GIF thumbnail as a separate photo file |\n| Quoted-post media | Media inside a quoted post, marked `mediaSource: \"quoted post\"` |\n| Author and post context | Text, date, engagement counts, author name/avatar/verification/followers, hashtags, mentions, links, reply/quote flags and quoted post IDs |\n| Profile context | Profile ID, follower count and media count on rows found through a username |\n| Profile filters | Minimum likes, minimum reposts and verified-authors-only |\n\n## How it works\n\nThe actor reads X's public post data and the FxTwitter mirror, combines the fields each source is best at, and falls back to the X status page and oEmbed when a post looks missing. Videos and GIFs use X's direct CDN; if a direct file cannot be fetched, yt-dlp is tried as a last resort. Files are streamed to disk, checked for a sane size, stored in the run's key-value store and linked with a signed public URL. No request uses your cookies, login or API key.\n\n## Input\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `postUrls` | array | – | Post links or bare post IDs (the prefill uses one NASA post) |\n| `usernames` | array | – | Handles, `@handles` or profile links to scan |\n| `maxPostsPerUser` | integer | `20` | Media posts to check per profile (1–1000) |\n| `maxItems` | integer | `100` | Hard cap on media rows for the whole run (1–10000) |\n| `mediaTypes` | array | `[\"video\", \"photo\", \"gif\"]` | `video`, `photo`, `gif` |\n| `imageQuality` | string | `orig` | `orig`, `large`, `medium`, `small` |\n| `videoQuality` | string | `best` | `best`, `2160p`, `1080p`, `720p`, `480p`, `worst` |\n| `downloadAllQualities` | boolean | `false` | One row and file per available MP4 quality |\n| `includePosterImage` | boolean | `true` | Save each video/GIF poster as a photo |\n| `includeAltText` | boolean | `true` | Attach X's image description when present |\n| `audioOnly` | boolean | `false` | Save only the audio track of videos; photos and GIFs are skipped |\n| `audioFormat` | string | `m4a` | `m4a` or `mp3` when audio-only is on |\n| `includeQuotedMedia` | boolean | `true` | Include media from a quoted post |\n| `includeMetadata` | boolean | `true` | Attach post, author and engagement context |\n| `minLikes` | integer | `0` | Profile scans only: skip posts below this like count |\n| `minRetweets` | integer | `0` | Profile scans only: skip posts below this repost count |\n| `onlyVerifiedAuthors` | boolean | `false` | Profile scans only: skip unverified authors |\n| `saveToFile` | boolean | `true` | Store files and return signed links; off means metadata and CDN links only |\n\nAt least one post link/ID or username is required. Directly linked posts are always collected; the three filters apply while scanning usernames.\n\n### Example: download two posts\n\n```json\n{\n  \"postUrls\": [\n    \"https://x.com/NASA/status/2107095768821576019\",\n    \"https://x.com/SheCodeAfrica/status/2107516524965794217\"\n  ],\n  \"videoQuality\": \"1080p\",\n  \"maxItems\": 10\n}\n```\n\n### Example: scan a profile's media\n\n```json\n{\n  \"usernames\": [\"NASA\"],\n  \"maxPostsPerUser\": 50,\n  \"maxItems\": 100,\n  \"mediaTypes\": [\"video\", \"photo\"],\n  \"minLikes\": 100,\n  \"onlyVerifiedAuthors\": true\n}\n```\n\n### Example: audio-only MP3\n\n```json\n{\n  \"postUrls\": [\"https://x.com/NASA/status/2107095768821576019\"],\n  \"audioOnly\": true,\n  \"audioFormat\": \"mp3\"\n}\n```\n\n## Output\n\nEvery dataset row has `type`: `media file` for a collectible file, or `status` for a run message.\n\n| Group | Fields |\n|---|---|\n| Identity | `tweetId`, `tweetUrl`, `postedAt`, `conversationId`, `inReplyToTweetId`, `isReply`, `isQuote`, `isRetweet`, `quotedTweetId`, `quotedTweetUrl` |\n| Media | `mediaType`, `mediaUrl`, `mediaUrlOriginal`, `contentType`, `mediaId`, `mediaKey`, `thumbnailUrl`, `posterImageUrl`, `filename`, `fileSizeBytes`, `width`, `height`, `durationSeconds`, `focusRects`, `allowDownload` |\n| Download | `downloadUrl`, `downloadStatus`, `message` |\n| Video quality | `variantQuality`, `variantBitrate`, `availableQualities`, `qualityCount`, `isBestQuality` |\n| Audio | `audioFormat` |\n| Author | `uploaderUsername`, `uploaderName`, `uploaderVerified`, `uploaderAvatarUrl`, `uploaderFollowers` |\n| Post context | `tweetText`, `likeCount`, `retweetCount`, `replyCount`, `quoteCount`, `bookmarkCount`, `viewCount`, `engagementRate`, `hashtags`, `mentions`, `urls` |\n| Source | `mediaSource` (`post`, `quoted post` or `poster image`) |\n| Profile | `profileId`, `profileFollowers`, `profileMediaCount` |\n| Run | `collectedAt` |\n\nEmpty fields are omitted. Status rows carry `status` (`noInput`, `invalidLink`, `invalidUsername`, `notFound`, `rateLimited`, `error`, `noMatchingMedia`, `noRecords`), a human-readable `message`, and the related `tweetUrl`, `tweetId` or `username`.\n\n### Example record\n\n```json\n{\n  \"type\": \"media file\",\n  \"tweetId\": \"2107095768821576019\",\n  \"tweetUrl\": \"https://x.com/i/status/2107095768821576019\",\n  \"postedAt\": \"2026-10-05T13:09:13+00:00\",\n  \"mediaType\": \"video\",\n  \"mediaUrl\": \"https://video.twimg.com/amplify_video/.../avc1/1920x1080/video.mp4?tag=16\",\n  \"contentType\": \"video/mp4\",\n  \"mediaId\": \"2107095744146460672\",\n  \"filename\": \"2107095768821576019_video_1_1080p.mp4\",\n  \"fileSizeBytes\": 3178943,\n  \"width\": 1920,\n  \"height\": 1080,\n  \"durationSeconds\": 13.3,\n  \"downloadUrl\": \"https://api.apify.com/v2/key-value-stores/{STORE_ID}/records/...?signature=...\",\n  \"downloadStatus\": \"finished\",\n  \"uploaderUsername\": \"NASA\",\n  \"uploaderName\": \"NASA\",\n  \"uploaderVerified\": true,\n  \"uploaderFollowers\": 92310189,\n  \"likeCount\": 7476,\n  \"retweetCount\": 1125,\n  \"quoteCount\": 97,\n  \"bookmarkCount\": 332,\n  \"viewCount\": 2961506,\n  \"variantQuality\": \"1080p\",\n  \"isBestQuality\": true,\n  \"mediaSource\": \"post\",\n  \"collectedAt\": \"2026-10-29T00:00:00+00:00\"\n}\n```\n\n## Usage cost\n\nYou pay the actor's per-result price shown on its Apify page plus normal Apify platform usage. With `saveToFile: false` no media bytes are stored, so a metadata-only run is the cheapest option. Downloads add key-value store usage proportional to the real file sizes. Keep `maxItems` and `maxPostsPerUser` at the values you need — the actor stops as soon as `maxItems` is reached.\n\n## FAQ\n\n**Do I need an X account, cookies or an API key?** No. Everything is read from public, logged-out sources.\n\n**Can I download from a private or protected account?** No. Protected and deleted posts return a `notFound` status row; they are not silently skipped.\n\n**How far back does a profile scan go?** The walk follows X's media timeline page by page until `maxPostsPerUser`, `maxItems` or the end of the timeline. Large profiles can be rate-limited; lower `maxPostsPerUser` and re-run later.\n\n**How are video qualities chosen?** The closest available quality to your preference is used. Portrait videos are compared by their short side, so a 1080×1920 video counts as 1080p. With `downloadAllQualities`, every progressive MP4 becomes its own row and file.\n\n**What happens with quoted posts?** Media inside a quoted post is included by default and marked `mediaSource: \"quoted post\"`. Turn `includeQuotedMedia` off to keep only the linked post's own media.\n\n**Does audio-only work on GIFs?** No. Animated GIFs have no audio track, so they are skipped in audio-only mode along with photos.\n\n**What if a post has no media?** You get one `noMatchingMedia` status row explaining it, not an empty dataset.\n\n**Do download links expire?** Signed key-value store links follow your Apify storage retention. Re-run the actor or copy the files to your own storage for permanent archiving.\n\n**How do the profile filters treat unknown counts?** A post whose like/repost count or verification status is unknown is kept rather than excluded.\n\n**Can I still use my saved inputs from the previous version?** Yes. Old input keys are mapped automatically in code, so saved tasks keep running.\n\n## Notes\n\n- All timestamps are UTC ISO 8601.\n- Engagement rate is derived from likes, reposts, replies and quotes divided by the author's follower count; it is omitted when the follower count is unknown.\n- Media URLs are direct X CDN links and open without a Referer header.\n- Download links are public signed links tied to the run's key-value store.\n- Use the actor only for content you have the right to download.\n\n## Limitations\n\n- Public posts only: deleted posts, protected accounts and login-gated content are not accessible.\n- Profile scans cover the most recent media posts X exposes publicly, up to 1,000 posts per run; deeper history is not addressable and very large profiles can hit rate limits, so re-run later if a scan stops early.\n- A single media file larger than 1 GB is rejected to keep memory bounded.\n- Some media is marked by X as not downloadable; those rows report `downloadStatus: \"notDownloaded\"` with an explanation.\n- Quote media depends on the quoted post still being public.\n- X changes its public endpoints periodically; the actor keeps several fallbacks, but a run can still return `rateLimited` or `error` status rows during an outage.\n",
      "input": {
        "title": "Twitter (X) Media Downloader",
        "type": "object",
        "schemaVersion": 1,
        "properties": {
          "postUrls": {
            "title": "Post links or IDs",
            "type": "array",
            "description": "Paste links of posts to download media from. Accepts x.com and twitter.com links, i/web/status links, or bare post IDs. Optional if you provide usernames or profile links below.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "prefill": [
              "https://x.com/NASA/status/2107095768821576019"
            ],
            "example": [
              "https://x.com/NASA/status/2107095768821576019",
              "2107516524965794217"
            ]
          },
          "usernames": {
            "title": "Usernames or profile links to scan",
            "type": "array",
            "description": "Scan a profile's media posts and download their photos, videos and GIFs. Type handles with or without @, or paste full profile links. Optional if you provide post links above.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "prefill": [],
            "example": [
              "NASA",
              "https://x.com/elonmusk"
            ]
          },
          "maxPostsPerUser": {
            "title": "Maximum media posts to check per username",
            "type": "integer",
            "description": "How many of each profile's most recent media posts to look at. Posts without matching media or filtered-out posts are skipped.",
            "default": 20,
            "prefill": 1,
            "minimum": 1,
            "maximum": 1000
          },
          "maxItems": {
            "title": "Maximum media files to collect",
            "type": "integer",
            "description": "Hard cap on dataset rows for the whole run. The actor stops collecting and downloading once this many files are emitted.",
            "default": 100,
            "prefill": 1,
            "minimum": 1,
            "maximum": 10000
          },
          "mediaTypes": {
            "title": "Media types to download",
            "type": "array",
            "description": "Choose which kinds of media to download. All types are selected by default.",
            "editor": "select",
            "items": {
              "type": "string",
              "enum": [
                "video",
                "photo",
                "gif"
              ],
              "enumTitles": [
                "Videos",
                "Photos",
                "GIFs"
              ]
            },
            "default": [
              "video",
              "photo",
              "gif"
            ],
            "prefill": [
              "video",
              "photo",
              "gif"
            ]
          },
          "imageQuality": {
            "title": "Photo quality",
            "type": "string",
            "description": "Quality to use for photos (and video poster images). Original is the full-resolution file posted on X.",
            "editor": "select",
            "default": "orig",
            "enum": [
              "orig",
              "large",
              "medium",
              "small"
            ],
            "enumTitles": [
              "Original (highest quality)",
              "Large",
              "Medium",
              "Small (lightest)"
            ],
            "prefill": "orig"
          },
          "videoQuality": {
            "title": "Video quality",
            "type": "string",
            "description": "Preferred video resolution. The actor picks the closest available quality when the exact one is not offered.",
            "editor": "select",
            "default": "best",
            "enum": [
              "best",
              "2160p",
              "1080p",
              "720p",
              "480p",
              "worst"
            ],
            "enumTitles": [
              "Best available",
              "2160p (4K)",
              "1080p (Full HD)",
              "720p (HD)",
              "480p (SD)",
              "Worst (smallest file)"
            ],
            "prefill": "best"
          },
          "downloadAllQualities": {
            "title": "Download every video quality",
            "type": "boolean",
            "description": "Emit and download one separate file for every progressive MP4 quality X offers (for example 1080p, 720p, 480p) instead of only the preferred one. Every quality gets its own row, filename and download link.",
            "default": false,
            "prefill": false
          },
          "includePosterImage": {
            "title": "Also save video poster images",
            "type": "boolean",
            "description": "Save the thumbnail/poster image of each video or GIF as an additional photo file.",
            "default": true,
            "prefill": false
          },
          "includeAltText": {
            "title": "Include image alt text",
            "type": "boolean",
            "description": "Attach the alt text (image description) X stores for each photo or video thumbnail to the media row.",
            "default": true,
            "prefill": true
          },
          "audioOnly": {
            "title": "Audio only (extract sound from videos)",
            "type": "boolean",
            "description": "Download only the audio track of videos. Photos and GIFs are skipped. Files are extracted with ffmpeg and file saving is enabled automatically.",
            "default": false,
            "prefill": false
          },
          "audioFormat": {
            "title": "Audio file format",
            "type": "string",
            "description": "Format used when Audio only is on.",
            "editor": "select",
            "enum": [
              "m4a",
              "mp3"
            ],
            "enumTitles": [
              "M4A (AAC, smaller, native)",
              "MP3 (widest compatibility)"
            ],
            "default": "m4a",
            "prefill": "m4a"
          },
          "includeQuotedMedia": {
            "title": "Include media from quoted posts",
            "type": "boolean",
            "description": "Also download the photos, videos and GIFs that appear inside a quoted post (a post embedded in the linked post). Those rows are marked with mediaSource \"quoted post\".",
            "default": true,
            "prefill": true
          },
          "includeMetadata": {
            "title": "Include post and author metadata",
            "type": "boolean",
            "description": "Attach the post text, date, conversation and reply/quote flags, hashtags, mentions, links, like/repost/reply/quote/bookmark/view counts, author name, avatar, verification and follower count, and a derived engagement rate to every downloaded file.",
            "default": true,
            "prefill": true
          },
          "minLikes": {
            "title": "Minimum likes (profile scans)",
            "type": "integer",
            "description": "When scanning usernames, skip posts with fewer likes than this. Posts whose like count is unknown are kept. Leave at 0 to keep every post.",
            "default": 0,
            "prefill": 0,
            "minimum": 0,
            "maximum": 1000000000,
            "sectionCaption": "Profile filters",
            "sectionDescription": "Optional filters applied while scanning usernames. Directly linked posts are always collected."
          },
          "minRetweets": {
            "title": "Minimum reposts (profile scans)",
            "type": "integer",
            "description": "When scanning usernames, skip posts with fewer reposts than this. Posts whose repost count is unknown are kept. Leave at 0 to keep every post.",
            "default": 0,
            "prefill": 0,
            "minimum": 0,
            "maximum": 1000000000
          },
          "onlyVerifiedAuthors": {
            "title": "Only verified authors (profile scans)",
            "type": "boolean",
            "description": "When scanning usernames, skip posts whose author is not verified. Posts where verification is unknown are kept.",
            "default": false,
            "prefill": false
          },
          "saveToFile": {
            "title": "Save files and return download links",
            "type": "boolean",
            "description": "Download each file into this run's key-value store and add a public signed download link. Turn off to collect metadata and direct CDN links only.",
            "default": true,
            "prefill": false
          }
        },
        "required": []
      },
      "output": {
        "actorOutputSchemaVersion": 1,
        "title": "Twitter (X) Media Downloader Output",
        "type": "object",
        "properties": {
          "media": {
            "type": "string",
            "title": "Downloaded media",
            "description": "One row per downloaded video, photo, GIF, audio track or poster image, with media metadata and a public download link. Status rows explain deleted, protected or media-less posts.",
            "template": "{{links.apiDefaultDatasetUrl}}/items"
          },
          "files": {
            "type": "string",
            "title": "Stored files",
            "description": "Key-value store holding the downloaded media files and their public links.",
            "template": "{{links.apiDefaultKeyValueStoreUrl}}/keys"
          }
        }
      },
      "dockerfile": "../Dockerfile",
      "categories": [
        "SOCIAL_MEDIA"
      ],
      "storages": {
        "dataset": {
          "actorSpecification": 1,
          "title": "Twitter (X) Media Downloader Output",
          "description": "One row per downloaded file, plus status rows for failures.",
          "fields": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "title": "Row type",
                "description": "media file for a downloaded file, status for a run message.",
                "enum": [
                  "media file",
                  "status"
                ],
                "example": "media file"
              },
              "tweetId": {
                "type": "string",
                "title": "Post ID",
                "description": "Numeric ID of the post the media belongs to.",
                "example": "2107095768821576019"
              },
              "tweetUrl": {
                "type": "string",
                "title": "Post URL",
                "description": "Canonical URL of the post.",
                "example": "https://x.com/i/status/2107095768821576019"
              },
              "mediaType": {
                "type": "string",
                "title": "Media type",
                "description": "video, photo or gif.",
                "enum": [
                  "video",
                  "photo",
                  "gif"
                ]
              },
              "mediaUrl": {
                "type": "string",
                "title": "Media URL",
                "description": "Media URL with the selected photo quality applied (videos/GIFs use the chosen stream).",
                "example": "https://pbs.twimg.com/media/ABC123?format=jpg&name=large"
              },
              "mediaUrlOriginal": {
                "type": "string",
                "title": "Original media URL",
                "description": "Untouched X CDN URL of the media, without the quality parameter.",
                "example": "https://pbs.twimg.com/media/ABC123"
              },
              "thumbnailUrl": {
                "type": "string",
                "title": "Thumbnail URL",
                "description": "Preview/poster image URL for videos and GIFs."
              },
              "posterImageUrl": {
                "type": "string",
                "title": "Poster image URL",
                "description": "Poster image URL on video/GIF rows when includePosterImage is on, with the selected photo quality applied."
              },
              "altText": {
                "type": "string",
                "title": "Alt text",
                "description": "Image description X stores for the photo or video thumbnail."
              },
              "filename": {
                "type": "string",
                "title": "File name",
                "description": "File name used in the key-value store.",
                "example": "2107095768821576019_video_1.mp4"
              },
              "fileSizeBytes": {
                "type": "integer",
                "title": "File size (bytes)",
                "description": "Real size of the downloaded file.",
                "example": 3178943
              },
              "width": {
                "type": "integer",
                "title": "Width",
                "description": "Media width in pixels.",
                "example": 1920
              },
              "height": {
                "type": "integer",
                "title": "Height",
                "description": "Media height in pixels.",
                "example": 1080
              },
              "durationSeconds": {
                "type": "number",
                "title": "Duration (seconds)",
                "description": "Video or GIF duration.",
                "example": 13.3
              },
              "audioFormat": {
                "type": "string",
                "title": "Audio format",
                "description": "Audio container used when the audio-only option is on.",
                "enum": [
                  "m4a",
                  "mp3"
                ]
              },
              "downloadUrl": {
                "type": "string",
                "title": "Download URL",
                "description": "Public signed download link for the stored file.",
                "example": "https://api.apify.com/v2/key-value-stores/STORE/records/file.mp4?signature=..."
              },
              "downloadStatus": {
                "type": "string",
                "title": "Download status",
                "description": "finished, failed or notDownloaded.",
                "enum": [
                  "finished",
                  "failed",
                  "notDownloaded"
                ]
              },
              "uploaderUsername": {
                "type": "string",
                "title": "Uploader",
                "description": "Handle of the account that posted the media.",
                "example": "NASA"
              },
              "tweetText": {
                "type": "string",
                "title": "Post text",
                "description": "Text of the post."
              },
              "likeCount": {
                "type": "integer",
                "title": "Likes",
                "description": "Number of likes on the post."
              },
              "retweetCount": {
                "type": "integer",
                "title": "Reposts",
                "description": "Number of reposts on the post."
              },
              "viewCount": {
                "type": "integer",
                "title": "Views",
                "description": "Number of views on the post."
              },
              "mediaSource": {
                "type": "string",
                "title": "Media source",
                "description": "post for the post's own media, quoted post for media inside a quoted post, poster image for a video thumbnail.",
                "enum": [
                  "post",
                  "quoted post",
                  "poster image"
                ]
              },
              "status": {
                "type": "string",
                "title": "Status code",
                "description": "Status code on status rows.",
                "enum": [
                  "noInput",
                  "invalidLink",
                  "invalidUsername",
                  "notFound",
                  "rateLimited",
                  "error",
                  "noMatchingMedia",
                  "noRecords"
                ]
              },
              "message": {
                "type": "string",
                "title": "Message",
                "description": "Human-friendly status or failure message."
              },
              "username": {
                "type": "string",
                "title": "Username",
                "description": "Username related to a status row."
              },
              "collectedAt": {
                "type": "string",
                "title": "Data collected at",
                "description": "UTC timestamp when the row was created.",
                "example": "2026-10-06T18:53:49+00:00"
              }
            },
            "required": [
              "type",
              "collectedAt"
            ]
          },
          "views": {
            "mediaFiles": {
              "title": "Downloaded media",
              "description": "One row per downloaded video, photo or GIF.",
              "transformation": {
                "fields": [
                  "tweetUrl",
                  "uploaderUsername",
                  "mediaType",
                  "filename",
                  "fileSizeBytes",
                  "width",
                  "height",
                  "durationSeconds",
                  "mediaUrl",
                  "mediaUrlOriginal",
                  "posterImageUrl",
                  "altText",
                  "downloadUrl",
                  "downloadStatus",
                  "postedAt",
                  "contentType",
                  "mediaId",
                  "uploaderName",
                  "uploaderVerified",
                  "quoteCount",
                  "quotedTweetUrl",
                  "collectedAt"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "tweetUrl": {
                    "label": "Post URL",
                    "format": "link"
                  },
                  "uploaderUsername": {
                    "label": "Uploader"
                  },
                  "mediaType": {
                    "label": "Type"
                  },
                  "filename": {
                    "label": "File"
                  },
                  "fileSizeBytes": {
                    "label": "Size (bytes)",
                    "format": "number"
                  },
                  "width": {
                    "label": "Width",
                    "format": "number"
                  },
                  "height": {
                    "label": "Height",
                    "format": "number"
                  },
                  "durationSeconds": {
                    "label": "Duration (s)",
                    "format": "number"
                  },
                  "mediaUrl": {
                    "label": "Media URL",
                    "format": "link"
                  },
                  "mediaUrlOriginal": {
                    "label": "Original Media URL",
                    "format": "link"
                  },
                  "posterImageUrl": {
                    "label": "Poster Image",
                    "format": "image"
                  },
                  "altText": {
                    "label": "Alt Text"
                  },
                  "downloadUrl": {
                    "label": "Download Link",
                    "format": "link"
                  },
                  "downloadStatus": {
                    "label": "Status"
                  },
                  "collectedAt": {
                    "label": "Data collected at",
                    "format": "date"
                  },
                  "postedAt": {
                    "label": "Posted at",
                    "format": "date"
                  },
                  "contentType": {
                    "label": "Content type"
                  },
                  "mediaId": {
                    "label": "Media ID"
                  },
                  "uploaderName": {
                    "label": "Uploader name"
                  },
                  "uploaderVerified": {
                    "label": "Verified"
                  },
                  "quoteCount": {
                    "label": "Quotes",
                    "format": "number"
                  },
                  "quotedTweetUrl": {
                    "label": "Quoted post",
                    "format": "link"
                  }
                }
              }
            },
            "allRecords": {
              "title": "All records",
              "transformation": {
                "fields": [
                  "type",
                  "status",
                  "message",
                  "tweetUrl",
                  "tweetId",
                  "mediaType",
                  "filename",
                  "downloadUrl",
                  "collectedAt"
                ]
              },
              "display": {
                "component": "table",
                "properties": {
                  "type": {
                    "label": "Row Type"
                  },
                  "status": {
                    "label": "Status"
                  },
                  "message": {
                    "label": "Message"
                  },
                  "tweetUrl": {
                    "label": "Post URL",
                    "format": "link"
                  },
                  "tweetId": {
                    "label": "Post ID"
                  },
                  "mediaType": {
                    "label": "Type"
                  },
                  "filename": {
                    "label": "File"
                  },
                  "downloadUrl": {
                    "label": "Download Link",
                    "format": "link"
                  },
                  "collectedAt": {
                    "label": "Data collected at",
                    "format": "date"
                  }
                }
              }
            }
          }
        }
      }
    },
    "buildNumber": "1.0.2",
    "imageDigest": "2ddffb183975a0857c516649a698b4f54cc34d7acf61d650f84d0e41862bbb92",
    "actVersion": {
      "sourceType": "SOURCE_FILES",
      "buildTag": "latest",
      "versionNumber": "1.0"
    },
    "usage": {
      "ACTOR_COMPUTE_UNITS": 0.017486666666666668
    }
  }
}