Bulk File Downloader - URLs, Images, Pages & Datasets
Pricing
from $4.00 / 1,000 file downloadeds
Bulk File Downloader - URLs, Images, Pages & Datasets
Bulk file downloader and bulk image downloader: download hundreds of public files, or every image on a list of web pages, in parallel from URL lists or another Actor's Dataset. Get one manifest row per file with checksums, status, size and download URL.
Pricing
from $4.00 / 1,000 file downloadeds
Rating
0.0
(0)
Developer
Vadim Bezrukov
Maintained by CommunityActor stats
0
Bookmarked
89
Total users
57
Monthly active users
9 days ago
Last modified
Categories
Share
Download every image from a list of web pages, or public HTTP/HTTPS files from a URL list or another Actor's Dataset. Files are streamed in parallel into Apify storage, and you get one manifest row per file with status, size, SHA-256 checksum and download URL.
Files are saved in the run's Key-Value Store. Open the Dataset to see which
downloads succeeded and follow each storage_url to retrieve the file.
Up to five downloads run at once by default, with a separate per-host limit. Total time depends on the file sizes and the servers hosting them.
The per-host limit applies to each redirect destination, including a shared CDN. Repeated HTTP 429/503 responses reduce that host to one active download after existing transfers finish.
Set fileName to choose an output name. Otherwise the Actor uses the response's
Content-Disposition filename (filename* takes priority), then the final URL.
Names are sanitized for storage; an invalid header falls back to the URL.
See Changelog for release history.
Download all images from web pages
Paste product, gallery or article pages into Web pages to download all images
from, or send them as pageUrlsText or pageUrls:
{"pageUrlsText": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html\nhttps://books.toscrape.com/catalogue/tipping-the-velvet_999/index.html","maxImagesPerPage": 50,"minImageBytes": 5000}
Each page is read once over plain HTTP, without a browser. The Actor collects the images in its HTML, downloads each one once and stores it like any other file:
imgwith the largestsrcsetcandidate, and the lazy-load attributesdata-src,data-srcsetanddata-lazy-src(they win over a placeholdersrc)- one image per
pictureelement: itsimg, or its firstsourcewhen theimgis only a placeholder og:image,twitter:imageandlink rel="image_src"- relative links resolved against
<base href>and the page URL; inlinedata:images are skipped and counted
The Dataset gets one row per page, followed by one row per image found on it.
Image rows keep the usual manifest fields and add source_page_url. Open the
Pages view for the page rows and Downloaded for the stored images.
Page status | Meaning |
|---|---|
PAGE_SCANNED | The page was read and images were found. page_images counts found, stored, failed and skipped images. |
NO_IMAGES_FOUND | The page was read, but its HTML lists no images. Pages that load images with JavaScript need a browser scraper first; pass its image URLs in urlsText or as a Dataset. |
PAGE_FETCH_FAILED | The page could not be fetched (error_code such as HTTP_404, TIMEOUT, or ROBOTS_UNAVAILABLE when robots.txt could not be read). |
ROBOTS_DISALLOWED | The site's robots.txt does not allow this page. It was not fetched. |
NOT_HTML | The URL returned a file, not an HTML page. Put direct file and image URLs in urlsText. |
PAGE_NOT_SCANNED | Not reached in this run: maxPages, the spending limit or the run timeout came first. |
page_images explains every image that was not stored: on_earlier_page (the
same image was already taken from an earlier page, so it is stored and charged
once), over_limit (beyond maxImagesPerPage, default 50), other_host
(skipped by sameHostOnly), too_small (below minImageBytes) and data_uris.
Only images are stored. A link that returns an HTML page, for example a login
page, gets NOT_AN_IMAGE and is not stored or charged. minImageBytes skips
icons and tracking pixels; such an image is fetched to measure it but not stored
or charged. sameHostOnly keeps images on the page's own host, which also
skips images on a separate CDN host, so leave it off for most shops.
Web page fields take priority over the file URL fields, so the demo URLs are not
downloaded alongside your pages. One list holds up to 500 pages, and maxPages
(default 100) limits how many are scanned per run. Pages themselves are not
charged; each stored image is one file-downloaded event.
After the run, RUN_SUMMARY shows the page and image counts. If something did
not finish, RETRY_PAGES_INPUT lists the pages to scan again (not scanned,
failed, or no image started) and RETRY_INPUT lists unfinished images of pages
that already have stored images, with their sourcePageUrl. Start each in a new
run. A stored image is never in either list, so it is not charged twice.
Already have image URLs from a product scraper? Use the product image recipe with SKU file names instead: it reads the URLs straight from that scraper's Dataset.
Quick start
For a plain list, paste links into File URLs (one per line) in Console, or send:
{"urlsText": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf\nhttps://www.rfc-editor.org/rfc/rfc791.txt"}
Blank lines and surrounding spaces are ignored. A non-empty urlsText takes
priority over urls, so the demo JSON links are not downloaded alongside your
list. Leave urlsText blank to use the existing JSON format with custom names:
{"urls": [{"url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf","fileName": "dummy.pdf"},{"url": "https://www.rfc-editor.org/rfc/rfc791.txt","fileName": "rfc791.txt"}],"concurrency": 5,"maxFileSizeMb": 100,"timeoutSeconds": 120,"maxRetries": 2,"overwrite": false}
Default Console / API prefill uses the same bounded public demo URLs.
Download files from another Actor
Select the upstream Actor's Dataset in Source Dataset, ideally after that
Actor finishes. Set urlField to the field containing one file URL per row:
{"datasetId": "YOUR_DATASET_ID","urlField": "image.url","metadataFields": ["productId", "sku", "SKU"],"maxItems": 1000}
Use the actual 17-character Dataset ID. datasetId overrides the demo URLs,
urlsText and urls. Dot paths such as image.url or images.0.url select
one value, without expanding arrays. Optional fileNameField selects a custom
file name. The Dataset must be accessible to your account; the input requests
read-only access.
Each output row preserves source_dataset_id, the absolute source_item_index,
and selected identifiers inside metadata, for example
{"productId": 123, "SKU": "BLUE-M"}. The defaults preserve productId, sku
and SKU. Missing identifiers are omitted. Select up to 20 scalar fields,
at most 1024 JSON bytes per value and 4096 bytes together. Invalid values or
missing URLs produce explicit failed rows. Unselected source fields are not copied.
The Actor reads pages of 50 rows and freezes the selected range before downloading.
maxItems defaults to 1000 and supports up to 10,000 rows per batch.
datasetOffset selects the starting row. NEXT_INPUT provides the next range
when more rows exist. Finish the current range before starting the next one.
Each range is independent, so URL deduplication does not span separate ranges.
Apify Datasets only grow, so rows added later never change a range already read.
If the upstream Actor is still adding rows, the run downloads the rows it read,
sets RUN_SUMMARY.datasetChangedDuringRead and leaves the new rows to NEXT_INPUT.
RUN_SUMMARY.datasetTotalIsExact is false when more rows exist; in that case
datasetTotalItems is a lower bound, because Apify's total counter can lag writes.
Download the files every time a scraper finishes
Open the scraper or its saved Task in Apify Console, go to Integrations, connect this Actor so it starts when a scraper run succeeds, and use this input:
{"datasetId": "{{resource.defaultDatasetId}}","urlField": "imageUrl","metadataFields": ["sku"],"maxItems": 1000}
Write the placeholder exactly as shown, in quotes and with no spaces inside the
braces. When the scraper run finishes, Apify replaces it with that run's Dataset
ID, so every run downloads the files of the new result. Set urlField,
metadataFields, fileNameField and maxItems as for any Dataset above.
The integration also adds a payload object to the input. This Actor accepts it
and does not read it. If the integration input has no datasetId, the run stops
with INVALID_INPUT and says what to set, before any file is downloaded or
charged, because only datasetId gives the run read access to that Dataset.
Spending limits, NEXT_INPUT and CONTINUE_INPUT work as in any Dataset batch.
Product images with SKU file names
A product scraper often stores one image URL per product row, for example
{"sku": "SKU-101", "imageUrl": "https://...", "active": true}. This input
downloads one file per row and keeps the SKU in each manifest row, so every
file can be matched to its product even when image names repeat:
{"datasetId": "YOUR_DATASET_ID","urlField": "imageUrl","metadataFields": ["sku"],"maxItems": 1000}
Repeated URLs in one batch already share one file and one file event, and rows
without a URL are not charged. To drop products you do not need, name files
after the SKU or remove duplicates across several batches, run
Dataset Cleaner first
with its "Prepare product images for a bulk download" recipe. Then pass the
Cleaner run's Dataset ID here and add "fileNameField": "imageName". The
cleaning step has its own price; skip it when every row already holds one URL
you want.
To receive the images as ZIP archives with one folder per SKU, add export
to the same input, see
Product images in a ZIP.
A row with a list such as images holds several URLs, and images.0.url
downloads only the first one. To download all of them, split the list into one
row per image before this Actor, for example in an n8n Code node, and keep the
SKU and image position on each row. If you need this built in, say so in an
Issue.
Continue after an interruption or spending limit
After the run stops, open its Key-Value Store and copy CONTINUE_INPUT into a
new run's JSON input. It contains resumeFromStoreId and resumeStoreIds. Copy both fields: the latter
grants read-only access to earlier files in a continuation chain. The new run inherits
the frozen source, field mapping and download settings, rebuilds the complete
manifest for that range, and downloads only unfinished files. Confirmed files
are reused without another file event; reused: true and file_store_id
identify those rows. Repeated URLs with different product IDs retain separate
output rows but share one file and one file event within the batch.
Keep all previous run storages until you no longer need their files. Reused
storage_url values still point to the original storage. Do not start concurrent
continuations or branch repeatedly from an older checkpoint: use the most recent
stopped run. Up to 20 continuation generations are supported. Each new run still
has the normal start event. A file already stored and charged is not charged
again. A transfer that is still running when the spending limit is reached is
not stored and is not charged.
If a charge acknowledgement is uncertain, continuation stops for reconciliation;
it never guesses that charging again is safe. A missing previously stored file
also stops continuation. Use a fresh batch to change field mappings or source
values. If interruption happened before CONTINUE_INPUT was saved, no file
downloads or file charges have started, so start the original input again.
Use a new run for continuation.
If Apify restarts or migrates a running run, the run continues on its own in
both URL-list and Dataset mode. Files already stored and charged are reused
without another file event, rows already in the Dataset are not added twice, and
RUN_SUMMARY.restartsRecovered records the restart. A charge that was in progress
at the restart is checked against Apify's charge record; if Apify does not confirm
it within a minute, the run stops with BILLING_UNCERTAIN for review instead of
charging again.
Large batches and the run timeout
Shortly before the run timeout, the Actor stops starting downloads and cuts
transfers that are still running. Files already stored finish their charge, and
every remaining row is published with RUN_TIME_LIMIT, so the run ends
SUCCEEDED with RUN_SUMMARY.status PARTIAL (or RUN_TIME_LIMIT when no file
finished) and runTimeLimitRows. It keeps 45 seconds plus half a second per
50 rows for this, at most 40% of the remaining time. Start CONTINUE_INPUT
(Dataset batches) or RETRY_INPUT (URL lists) in a new run, or raise the run
timeout. A run without a timeout is not stopped early.
Checkpoints contain selected source identifiers and URLs, including signed query parameters. Treat them like the input and keep them private. This integration reduces workflow setup; it does not guarantee higher throughput or sales.
Example workflows
Choose a batch you already have permission to download:
- Public PDF documents: replace the first demo URL with a document URL and
set
fileNameto a recognizable name. Inspect itsSUCCESSrow before expanding the batch. - Mixed files: use the PDF and text URLs in the quick start above. The Dataset manifest keeps each input's status, checksum, size and storage URL, including explicit failures.
- Recurring document batches: pass the next list of document URLs from
your workflow and use
sha256downstream to compare file contents. Each run stores its own files; the Actor does not maintain a change-monitor baseline.
Download the files from your result
Open the run's Dataset and select a row with status == "SUCCESS". Its
storage_url points to that file in the run's Key-Value Store; open it using
your authorized Apify access, or pass it to the next step of your automation.
Keep the manifest if you need to match downloaded files back to their input
URLs. Failed rows do not represent downloadable files. Files remain subject
to your Apify storage retention settings. To get them as ZIP archives with one
folder per SKU, see Product images in a ZIP.
Retry unsuccessful files
After a completed batch, read RUN_SUMMARY. If retryStatus is AVAILABLE,
open the Retry input output link or the RETRY_INPUT record in the run's
Key-Value Store. Copy its complete JSON into a new run. It contains only failed
URLs, deduplicated, with original explicit file names and the same download
settings. Its blank urlsText prevents an old pasted list from overriding it.
Successful files are excluded. No second run starts automatically.
Review the Failed Dataset view first: expired URLs, missing files, security rejections and size limits may need correction. Set the new run's spending limit, memory and timeout separately; these platform run options are not part of Actor input. A later successful download is billed normally. Files from the first run remain in that run's storage; the new run contains only retry results.
retryStatus: NOT_NEEDED means there is no retry input. If billing has an
unknown outcome, retryStatus: BLOCKED_BILLING_UNCERTAIN suppresses the entire
retry input until charges are reconciled. RETRY_INPUT is absent in both cases
and is not promised for runs interrupted before the batch result is saved.
Like INPUT and Dataset provenance, it retains exact URLs, including signed
query parameters: keep it private.
Product images in a ZIP, one folder per SKU
Turn a product Dataset into ZIP archives with one folder per SKU, images in order and JSON/CSV manifests, ready for a catalog migration, a marketplace upload or a hand-off to a supplier. The ZIP adds no charge: you pay for the stored files exactly as in any Dataset batch.
Export reads a Dataset (datasetId) or continues one (resumeFromStoreId).
A run that sets export with pasted URLs or web pages stops as invalid input,
because those rows carry no product fields to name folders by. Runs without
export work exactly as before.
For a Dataset with sku, imageUrl and an optional zero-based imageIndex:
{"datasetId": "YOUR_DATASET_ID","urlField": "imageUrl","maxItems": 1000,"export": {"mode": "zip","skuField": "sku","imageIndexField": "imageIndex"}}
Images appear as SHOE-001/001.jpg, SHOE-001/002.jpg and so on. Each ZIP
part contains manifest.json and manifest.csv for its source rows, including
failures; combine the manifests of all parts for the complete batch.
RUN_SUMMARY.exportArtifacts lists the parts with their storage keys, sizes and
checksums. The archive is built on disk, so a large export does not need a
large amount of memory.
SKU folders and image names
skuField, productIdField, imageIndexField and imageRoleField use the
same dot-path rules as urlField. Defaults are sku, productId,
imageIndex and imageType. A SKU must be a nonblank string of at most 512
UTF-8 bytes. Product ID is optional, a string or integer of at most 256
characters. Image index is optional, an integer from 0 to 999999999. Role, when
supplied, is main, gallery or detail.
Missing image indices use the order of the rows in the batch. Repeated indices get a suffix with the absolute source row number. Unsafe, reserved or case-colliding SKUs get a folder derived from their SHA-256, and the original SKU stays in the manifest. Names stay the same through retries and continuations of the same batch.
A URL used by several SKUs is downloaded and charged once and appears in every
SKU folder. Image bytes are not changed. The file extension comes from the
image signature, so a PNG served as image/jpeg is still saved as .png.
Supported formats: JPEG, PNG, GIF, WebP, TIFF, BMP and AVIF. Other formats,
including SVG and HEIC, get UNSUPPORTED_IMAGE_FORMAT in the manifest; the
downloaded file stays in storage and is charged like any stored file. This is
format identification, not image decoding or a quality check.
Export limits, report and recovery
| Input | Default | Range and behavior |
|---|---|---|
maxArchiveSizeMb | 256 | 1-4096 MiB per ZIP part, including manifests and ZIP overhead. |
maxTempDiskMb | 512 | 2-8192 MiB of temporary disk: at most half for the archive, half for the source files. |
maxExportSizeMb | 10240 | 1-102400 MiB of image bytes per batch, counting each SKU copy. |
maxExportItems | 10000 | 1-10000 SKU images per batch. |
A ZIP part holds at most one 50-row page of the batch and splits further by
size. An image too large for one part gets ARCHIVE_ITEM_TOO_LARGE; it is not
truncated. Rows over an export limit stay in the manifest with
BUDGET_EXCEEDED. These limits apply after downloading, which keeps its own
maxItems, maxFileSizeMb and spending limit.
Download status and error_code keep their meaning. Export adds
export_status and export_error_code: SUCCESS, INVALID_INPUT,
SOURCE_FAILED, STORAGE_FAILED, EXPORT_FAILED or BUDGET_EXCEEDED. An
invalid SKU never turns a stored file into a failed download, and the file
charge stays as it was.
RUN_SUMMARY adds the source rows, unique files, repeated SKU images, failed
downloads, exported and failed images, the ZIP parts and
exportBytesWritten / exportObjectsWritten / exportObjectsReused for this
run. Each finished export run also writes EXPORT_REPORT.html to its
Key-Value Store: the outcome, counts, ZIP parts, the first 200 rows that need
attention with a suggested fix, and links to the Dataset and CONTINUE_INPUT.
The report has no scripts, external resources or public links.
RUN_SUMMARY.exportReportStatus is WRITTEN, FAILED, or SKIPPED when too
little time was left before the run timeout. A run stopped by the platform, for
example at its timeout, has no report, so read RUN_SUMMARY first.
Shortly before the run timeout the Actor stops on its own, so a long export
still ends SUCCEEDED with RUN_SUMMARY.status RUN_TIME_LIMIT (or PARTIAL
when some images were exported). To finish it, copy CONTINUE_INPUT from the
latest stopped run into a new run and keep the earlier run storages. Finished ZIP parts are
reused, a missing part is rebuilt from the stored files, and no stored file is
downloaded or charged again. A missing paid file stops the continuation instead
of being downloaded and charged a second time. To fix source rows, such as a
blank SKU, export the corrected rows as a new batch: CONTINUE_INPUT replays
the original batch. Run one continuation at a time.
Export to your own Amazon S3 bucket is planned after November 5, 2026. It will
use the s3-uploaded-10mib event described in Pricing.
Related tools for finding or monitoring files
- Clean the Dataset before downloading: Dataset Cleaner filters rows, builds file names from other fields and removes duplicates across a whole Dataset. Pass its output Dataset ID as
datasetId. - Get attachment URLs from a paginated API: Paginated REST API Fetcher exports API items across a configured pagination pattern. Select permitted public file URLs from the item data and map them into this Actor's
urlsinput. - Monitor a PDF across revisions: PDF Change Monitor keeps a last-good baseline for a direct PDF or a PDF selected from a stable webpage. Choose it when you need content and link change events, rather than a fresh download manifest.
- Read HTML as Markdown: Website Content Crawler extracts authorized website text and optional chunks for AI and RAG. Give it the original HTML page URL and rights declaration; it does not parse the downloaded binary files.
Each linked tool has separate input and pricing. Select the URLs and start the appropriate run yourself or through your own automation.
Output example
Successful row:
{"source": "parallel-bulk-file-downloader","source_id": "…","status": "SUCCESS","source_url": "https://example.com/files/report.pdf","final_url": "https://cdn.example.com/report.pdf","source_index": 0,"file_name": "report.pdf","content_type": "application/pdf","bytes": 4123456,"sha256": "…","kvs_key": "files-000000-report.pdf","storage_url": "https://api.apify.com/v2/key-value-stores/…/records/files-000000-report.pdf","http_status": 200,"attempts": 1,"duration_ms": 842,"error_code": null,"error_message": null,"downloaded_at": "2026-09-17T21:00:00Z","scraped_at": "2026-09-17T21:00:00Z","fingerprint": "…","schema_version": 2}
Dataset views: All, Downloaded, Failed.
Automation / API use
curl -X POST "https://api.apify.com/v2/acts/automa-flow~parallel-bulk-file-downloader/runs" \-H "Authorization: Bearer $APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"urls":[{"url":"https://www.rfc-editor.org/rfc/rfc791.txt","fileName":"rfc791.txt"}],"concurrency":5}'
Schedule a recurring URL batch or call the Actor from n8n / Make / your backend. A second run is useful whenever you have a new URL list, a refreshed CDN export, or need checksums again for integrity checks.
AI agents / MCP
Direct tool URL (authenticated; each caller uses their own Apify OAuth or token):
https://mcp.apify.com?tools=automa-flow/parallel-bulk-file-downloader
Anonymous MCP discovery (search-actors) indexes this public Store Actor.
The direct tool URL above is the authenticated path for initialize,
tools/list and a paid run. Direct agentic payment needs pay-per-event prices
with platform usage included, limited permissions and no Standby mode. This
Actor meets those product rules once the included-usage price below is the
active Store price. Developer identity verification is a separate Apify
requirement and is not claimed here.
When an agent should call it: the user already has a list of public
HTTP/HTTPS file URLs, or a list of web pages whose images they need, and needs
the files in Apify storage plus a per-file manifest (status, storage_url,
SHA-256) for a downstream step (CRM attachment sync, product image import,
integrity re-check, report pack).
When not to call it: crawling a whole site (only the listed pages are read), galleries that need JavaScript to show their images, HTML text extraction, authenticated Drive/Dropbox, video-site downloaders, or any private/localhost URL (SSRF policy rejects those per item).
The event price is the whole bill. Platform usage is included. It is not added on top.
| Example | Event charges at 1 GiB |
|---|---|
| Two stored files | $0.00805 ($0.00005 start + 2 × $0.004) |
| 100 stored files or images | $0.40005 |
| One 256 MiB file, the maximum size | $0.00405 until November 5, 2026, then $0.07905 |
Always set maxTotalChargeUsd (Store minimum $0.00405, enough for one file
of up to 10 MiB). From November 5, 2026 a larger file needs room for its size
units as well. The cap limits event charges. At the cap, already stored files
remain. A file that would not fit is not stored and is not charged. Remaining
URLs get BUDGET_EXCEEDED or cancelled rows, and RETRY_INPUT lists them for a
new run with a higher limit.
Example agent ask: “Download these public PDF URLs in parallel with
maxTotalChargeUsd=0.05, then read RUN_SUMMARY and return Dataset rows with
storage_url and sha256 for SUCCESS items only.”
After the run, read Key-Value Store RUN_SUMMARY first (status,
successful / failed, chargedEventCounts, nextAction), then the default
Dataset. Output schema also exposes runSummary, billingReceipt and conditional retryInput links.
Failure semantics
error_code | Meaning |
|---|---|
SECURITY_REJECTED | URL/host/IP failed SSRF policy (localhost, private, metadata, bad scheme). |
HTTP_404 / HTTP_403 / … | Remote server returned that status. |
HTTP_ERROR | Unusable HTTP response, including partial HTTP 206 or HTTP 204/205 without a file. |
RATE_LIMITED | HTTP 429 after retry budget. |
TIMEOUT | Per-attempt source-download deadline exhausted, including DNS, redirects and body transfer. |
FILE_TOO_LARGE | Content-Length or streamed bytes exceeded maxFileSizeMb. |
STORAGE_FAILED | Key-Value Store write failed after a successful download. |
BUDGET_EXCEEDED | Spending limit stopped storing/charging further successful files. If the limit cannot cover even one file, every URL gets this code, the run succeeds with RUN_SUMMARY.status: BUDGET_EXCEEDED and RETRY_INPUT holds all URLs. |
BILLING_UNCERTAIN | Charge API failed after a store write; the run fails closed (no replay). |
RUN_TIME_LIMIT | The run timeout arrived before this file finished. Nothing was stored or charged for it; continue in a new run. |
NOT_AN_IMAGE | Web page mode: the image link returned something else, such as an HTML page. Not stored or charged. |
ROBOTS_DISALLOWED / ROBOTS_UNAVAILABLE | Web page mode: robots.txt does not allow the page, or could not be read. The page was not fetched. |
MAX_PAGES_REACHED | Web page mode: the page is beyond maxPages; RETRY_PAGES_INPUT lists it for the next run. |
NO_RESULTS is not used: when every URL and page input is empty, runtime
validation returns INVALID_INPUT and the run fails without downloading files.
In web page mode, pages without images end as NO_IMAGES_FOUND with their page
rows. When every page failed with a network, server or robots.txt error, the
run ends COMPLETED_WITH_FAILURES, like a list of failed file URLs: each page
row keeps its error and nothing is downloaded or charged. Malformed
text lists and field bounds are rejected by the platform before a run starts.
An ordinary item failure never cancels independent downloads. A
BILLING_UNCERTAIN outcome stops the batch, cancels and drains sibling workers,
deletes files known to be uncharged, and retains the uncertain file plus its
Dataset row for reconciliation.
timeoutSeconds excludes queue time, retry backoff and storage uploads. Slow
sources may need a higher value. A valid empty file returned with HTTP 200 is
accepted; partial downloads and no-content responses are not complete files.
Pricing
Pay per event. Platform usage (compute, transfer and storage) is included in these prices. There is no separate platform usage charge.
| Event | Price |
|---|---|
apify-actor-start | $0.00005 per run at 1 GiB. Apify charges one event per GiB of memory. |
file-downloaded | $0.004 per successfully stored unique file or image. It includes the first 10 MiB of the file. |
stored-10mib | From November 5, 2026: $0.003 for each started 10 MiB of a stored file after its first 10 MiB. |
s3-uploaded-10mib | For the upcoming S3 export only: $0.004 for each started 10 MiB the run uploads to your S3 bucket. |
Until November 5, 2026 every stored file is $0.004, whatever its size. From that date a file larger than 10 MiB also pays its size units. Most images and documents are 10 MiB or smaller, and their price does not change.
| Stored file | Event charges from November 5, 2026 |
|---|---|
| 10 MiB or smaller | $0.004 |
| 25 MiB | $0.010 |
| 100 MiB | $0.031 |
| 256 MiB, the maximum size | $0.079 |
Not charged: failed URLs, retries, redirects, security rejections, oversized responses, web pages, images that are not images or are below minImageBytes, or duplicate copies of a URL or image that was already stored in the same run.
s3-uploaded-10mib is listed in advance for the planned S3 export. No run
charges it until that export is released and you choose S3 as the destination.
Downloads and ZIP archives never pay it. A ZIP export adds no event of its own.
Security and SSRF protection
- Only
httpandhttpson ports 80/443. - Localhost, loopback, RFC1918, link-local, multicast, reserved, IPv6 ULA and cloud metadata targets are rejected.
- Every redirect is re-validated (scheme + DNS + address policy), max 5 hops. HTTPS to HTTP downgrades are rejected.
- TCP connects use DNS pinning so validation cannot silently reconnect to a different address (DNS rebinding defense).
- Filenames are sanitized; remote path segments are never used as local paths.
- Query strings may contain signed URL parameters; logs redact query values.
Dataset rows intentionally retain the exact
source_urlandfinal_urlfor retry/provenance. Keep the Dataset private and avoid signed URLs if retaining those values would expose credentials. - Key-Value Store uploads are serialized because the Apify client buffers each
record body; concurrency is also capped by run memory versus
maxFileSizeMb.
Responsible use and retention
Download only pages and files you are authorized to copy and re-host. The Actor does not bypass authentication, paywalls, robots controls, or access restrictions, and robots.txt is respected for every web page it reads. A public URL does not by itself grant copyright or redistribution rights; product photos and gallery images usually belong to their creators. Inputs, Dataset rows, and generated storage URLs can contain personal, confidential, or regulated data. Keep run storages private, apply the shortest practical retention period, delete unneeded Key-Value Stores/Datasets, and do not use the Actor for prohibited or illegal content.
Limits
- 1-500 URLs from
urlsTextorurls, up to 10,000 Dataset rows per batch, or up to 500 web pages per list withmaxPages(default 100, at most 500) scanned per run andmaxImagesPerPage(default 50, at most 100). Provide a URL, page or Dataset source, orresumeFromStoreId; this is checked at runtime. - Concurrency 1–20 (default 5).
- Exact duplicate URLs are downloaded, stored, and billed once; duplicate rows
share the first occurrence's storage key and
fileName. - Max file size 1–256 MiB (default 100). Runs require at least 1 GiB memory because the Apify KVS client buffers one serialized upload body.
- No browser, FTP, Drive/Dropbox auth, cookies or HTTP byte-range resume in this version. Web page mode reads the HTML of each listed page only; it does not follow links or run JavaScript.