# Bulk File Downloader: Download Files from URLs to ZIP (`data-done-right/bulk-file-downloader`) Actor

Bulk file downloader API: download files from URLs, or the PDFs, documents and media linked on web pages, in one run. Dedupe by SHA-256, allow or block extensions, get public links and one URL to ZIP archive. Pay per file.

- **URL**: https://apify.com/data-done-right/bulk-file-downloader.md
- **Developed by:** [Data Done Right](https://apify.com/data-done-right) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.28 / 1,000 files

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Bulk File Downloader: Download Files from URLs to ZIP

This **bulk file downloader** takes a list of direct file links (or web pages that link to files) and saves every file to a key-value store with a public link, its real type, its size and a SHA-256 hash. It removes duplicates, lets you allow or block file extensions, and packs everything into **one ZIP**. It is a **bulk file downloader API** for PDFs, Word and Excel files, CSVs, audio, video, archives, images and any other file type. No browser, no login and no API key.

> **Rights and responsibility.** You are responsible for having the right to download and reuse the files you fetch, and for following the terms of the sites you point it at. This Actor **does not log in, sends no cookies and no forged Referer, does not work around hotlink protection, and does not download from video or file-sharing pages** (those return web pages, which are never saved). If a host answers 401, 403 or 429, you get a `failed` row with that reason. For `pageUrls` it reads and obeys the site's `robots.txt`.

### What can this bulk file downloader do

- **Download files from URLs.** One dataset row per URL with `status`: `downloaded`, `duplicate`, `skipped` or `failed`, and a reason when it is not `downloaded`.
- **Download all the files linked on a page.** Add page links in `pageUrls`: the Actor reads `<a href>`, `<source>`, `<embed>`, `<object>`, `<audio>` and `<video>` links, resolves relative links (including `<base href>`) and downloads the ones that end in a wanted extension (pdf, docx, xlsx, zip, csv, mp3, mp4...).
- **Know what you really got.** The type is detected from the file's bytes, not the file name or the server header. A `.docx` link is stored as `docx`, a download link without an extension that sends a PDF is stored as `pdf`, and a `.pdf` link that sends an error page is reported as `failed` instead of being saved as a "PDF".
- **Allow or block extensions.** `allowedExtensions` keeps only the types you want. `blockedExtensions` refuses programs, installers, scripts and web pages by default (exe, msi, bat, cmd, sh, dll, jar, js, html and more). Web pages (HTML) are never saved, and a program or script you unblock is stored as `.bin`, never as `.exe`.
- **Remove duplicates.** Identical files (same SHA-256) are delivered once; later copies get status `duplicate` with a link to the first copy, and are free.
- **URL to ZIP.** Turn on `zip` to get all delivered files in one `files.zip` with a `manifest.csv` (file, source URL, source page, SHA-256, size), with a public link. File names inside the ZIP are sanitised (no folders, no `..`), and the ZIP has a size cap.
- **Polite and robust.** Per-host limits and delay, retries with backoff, a size cap checked on the header and while streaming, redirects checked hop by hop, and blocked private addresses.

### How to download files from URLs

1. Paste direct file links in **File URLs** (`urls`), one per line, and/or page links in `pageUrls`.
2. Optionally set `allowedExtensions`, `maxFileSizeMb`, `dedupe`, `zip` and `keepOriginalNames`.
3. Run it. Each file is saved in the run's key-value store under `<sha256>.<ext>`, and the dataset lists its public link (`fileUrl`). With `zip` on, the SUMMARY record has `zipUrl`.

### Input example

```json
{
  "urls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    "https://people.sc.fsu.edu/~jburkardt/data/csv/addresses.csv",
    "https://www.w3.org/Icons/w3c_main.png",
    "https://jsonplaceholder.typicode.com/todos/1"
  ],
  "zip": true
}
```

More examples: `"pageUrls": ["https://example.com/reports"], "allowedExtensions": ["pdf", "xlsx"]` (every PDF and Excel file linked on a page), `"keepOriginalNames": true` (ZIP entries named like the originals), `"maxFileSizeMb": 20, "maxFiles": 500` (tighter limits for a big list).

### Output

One dataset row per URL:

```json
{
  "url": "https://www.w3.org/Icons/w3c_main.png",
  "finalUrl": "https://www.w3.org/assets/logos/w3c-2025-transitional/w3c-full-315x48.png",
  "status": "downloaded",
  "reason": null,
  "contentType": "image/png",
  "extension": "png",
  "sizeBytes": 7816,
  "sha256": "03ab7291bbb7f7e8bb31f13144e107d48293a87e4bcfb2f25e77cdb2f4e183ab",
  "fileUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/03ab7291...183ab.png",
  "sourcePage": null,
  "fileName": "w3c-full-315x48.png",
  "key": "03ab7291bbb7f7e8bb31f13144e107d48293a87e4bcfb2f25e77cdb2f4e183ab.png",
  "index": 3
}
```

- `status`: `downloaded` (charged), `duplicate` (same content as an earlier file, or the same URL listed twice), `skipped` (your rules: extension, size, spending limit, robots.txt) or `failed` (network error, HTTP error, a web page instead of a file, a file that is not what its URL says, blocked address).
- `reason`: why a URL was not downloaded, or a note on a downloaded file (for example `URL ends in .jpg but the file is png; saved as .png`).
- `extension` is what the file is stored with: from its bytes, then the URL, the `Content-Disposition` name and the server's content type. `bin` means unknown, or a program or web extension that was refused.
- The key is `sha256` plus the extension, so it is always safe and never contains parts of the URL. `fileName` is the sanitised original name.
- `sourcePage` is the page a file link was found on (`pageUrls`), empty for URLs you listed yourself.
- The `SUMMARY` record has the counts, the counts per extension, the charged units, `zipUrl` and any notes.

### Bulk file downloader API

Call this Actor from the Apify API, Python, JavaScript, Make, Zapier or n8n. Example with the Apify Python client:

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("data-done-right/bulk-file-downloader").call(run_input={
    "urls": ["https://example.com/report.pdf", "https://example.com/data.csv"],
    "allowedExtensions": ["pdf", "csv"], "zip": True})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["status"], row["url"], row["fileUrl"])
summary = client.key_value_store(run["defaultKeyValueStoreId"]).get_record("SUMMARY")["value"]
print(summary["zipUrl"])
```

Files are read back from the run's key-value store (`client.key_value_store(run["defaultKeyValueStoreId"])`) or by their `fileUrl`.

### Use it in Claude, ChatGPT and Cursor

Connect Apify's MCP server (mcp.apify.com) and your AI assistant can fetch files for you. For example: "download these 40 PDF links, skip duplicates, and give me the ZIP link". The only input you need is a list of file URLs (`urls`) or a page URL (`pageUrls`).

### Bulk download files: use cases

- **Archive documents:** turn a spreadsheet of PDF, DOCX or XLSX links into files you own, with hashes to verify them.
- **Collect open data:** download the CSV, JSON and ZIP files a statistics office or data portal links on its pages.
- **Mirror your own assets:** back up the images, audio and video files of a site you run.
- **Feed other tools:** hand the stored files to a PDF-to-text, file conversion or dataset pipeline.
- **Check links:** see which file URLs are dead, are really web pages, or are too large.

### URL to ZIP

Turn on `zip` and every delivered file is also put into `files.zip` in the run's key-value store, with `manifest.csv` listing each entry's source URL, source page, SHA-256 and size. By default entries are named `0001-name.ext`; with `keepOriginalNames` they are `name.ext` (a counter is added if two files share a name). Names are sanitised: only letters, digits, dot, dash and underscore, never a folder or `..`. `zipMaxMb` (default 100, up to 300) caps the ZIP: files that would not fit are still saved and linked in the dataset, but left out of the ZIP, and the SUMMARY says how many. Already compressed formats (images, archives, Office files, audio, video) are stored without recompressing. The ZIP is free.

### How much does it cost

Pay per event, only for files that are delivered:

- **$0.0016 per downloaded file** (`file`). 100 small files cost about $0.16.
- **$0.0006 per started 10 MB block beyond the first 10 MB** of each file (`megabyte-block`), so large files cost fairly. A 25 MB file starts 3 blocks and costs the file price plus 2 blocks, $0.0028. A file of 10 MB or less costs just the file price. 10 MB is 10,000,000 bytes, measured on the delivered file.
- **Free:** duplicates, skipped and failed URLs, the ZIP, the dataset and the `SUMMARY` record.

Paid Apify plans get 10% (Silver) to 20% (Gold and up) off. `maxFiles` caps the number of URLs, `maxFileSizeMb` caps each file, and if you set a maximum spend per run the Actor stops cleanly when it is reached, keeping the files already delivered (the remaining URLs get a `skipped` row).

### FAQ

#### How do I download files from a list of URLs?

Put the links in `urls` and run. Every link gets a row; the files that were delivered have a `fileUrl` you can open or download. Turn on `zip` to also get everything in one ZIP.

#### How do I download all the PDFs (or other files) linked on a web page?

Put the page link in `pageUrls` and set `allowedExtensions` to the types you want, for example `["pdf"]`. The Actor fetches the page (respecting its robots.txt), finds the links and downloads those files. Without `allowedExtensions` it follows links to documents, archives, audio, video and images. Links are read from the HTML as served, so links that a page only creates with JavaScript are not found. Links are not followed into other pages: it reads each page you list, one level, and it does not crawl.

#### Does it respect robots.txt?

For `pageUrls`, yes: a page that `robots.txt` disallows for the `BulkFileDownloader` agent (or `*`) is not fetched and gets a `skipped` row. If `robots.txt` cannot be read because of a server error, the page is not fetched either. Direct file links you list yourself in `urls` are not checked against robots.txt, because you chose them; you are responsible for the right to download them. Files linked from a page are fetched like any other file link: `robots.txt` is applied to the page, not to the files it points to.

#### Which files are refused?

Web pages (HTML) are never saved: a link that returns one gets a `failed` row that points you to `pageUrls`. Programs, installers and scripts (exe, msi, bat, cmd, com, scr, ps1, sh, dll, jar, vbs, hta, js, apk, dmg, pkg, deb, rpm) are blocked by default, both by the URL ending and by the file's first bytes. If you remove them from `blockedExtensions` they are stored as `.bin` with the type `application/octet-stream`, never with a program extension. Other types are stored with their real type only when it is a harmless download type; XML is stored as a plain download, and SVG files are saved (with their image type) only after a check that they contain no scripts or active content; otherwise they are skipped.

#### How is the extension decided?

From the bytes first (PDF, PNG, JPG, ZIP, MP3, MP4, GIF, WebP, 7z, RAR and more have fixed signatures). ZIP-based formats (docx, xlsx, pptx, odt, epub) take the extension from the URL, the `Content-Disposition` file name or the content type. Text formats without a signature (CSV, JSON, TXT) take it from the URL, then the file name, then the content type. A name ending in `.php` or `.asp` names the script, not the file, so it is ignored. If nothing says, the extension is `bin`.

#### Can it get files behind a login or bypass hotlink protection?

No. It sends no cookies, credentials or Referer, and it does not pretend to be a browser: its User-Agent says it is a bot. Links with a user name or password in the URL are refused. Pages of login-walled social sites (Instagram, Facebook, LinkedIn, X, TikTok) are not supported. A host that refuses the download gives a `failed` row with the HTTP status. Share links of cloud drives (Google Drive, Dropbox, OneDrive) usually return a web page, so they fail with a clear message; use the direct download link if the owner provides one.

#### How are duplicates found?

By the SHA-256 of the downloaded bytes. The same URL listed twice is fetched only once. Turn `dedupe` off to deliver (and be charged for) every copy.

#### How big can a file be?

100 MB by default and at most. The limit is checked on the `Content-Length` header and again while streaming, so an oversized or endless response is cut off early. Files that are over the limit are `skipped` and free. The ZIP can hold up to 300 MB.

#### How long are the file links kept?

Files are saved in the run's default key-value store, which expires after your plan's data retention period like other unnamed storages. Set `outputStoreName` to save them in a named store that stays until you delete it. Anyone who has a `fileUrl` can open it.

#### Is it safe to give it any URL?

Links to localhost, private networks, link-local and cloud-metadata addresses are blocked, including names that resolve to them and redirects into them (every redirect hop is checked again; up to 5 are followed). When you use a proxy, the proxy opens the connection, but the same address checks still run before every request.

#### Is it polite to the servers?

Requests to the same host start at least `perHostDelayMs` apart (250 ms by default) and at most `perHostConcurrency` (3 by default) run at once per host. So a higher `concurrency` only speeds up lists that are spread across several hosts. The `timeoutSecs` clock for a file starts when its turn at the host comes, not while it waits in line. Network errors, timeouts and 429/5xx answers are retried up to `maxRetries` times (2 by default) with backoff, and `Retry-After` is honored. 401, 403 and 404 are not retried.

#### Do I need an API key or login?

No.

### Limits

- Direct links only: it does not click, log in, run JavaScript or crawl. Pages are read one level deep.
- 100 MB per file, 300 MB per ZIP, up to 10,000 URLs per run.
- Streaming sites, social networks and file-sharing pages are not supported.
- File type detection works on the first bytes. Plain text formats are identified by the URL or the server, so a CSV that is served as `text/plain` from a link without an extension is stored as `txt`.

### Related tools

- **Download Images from URLs: Bulk Image & File Downloader** is the image-focused sibling: resize, convert to JPG, PNG or WebP and filter by dimensions.
- **All-in-One File Converter: PDF, Office, Images, Audio & Video** converts the files you download to other formats.
- **PDF to Text & Markdown Converter with Automatic OCR** reads the text in the PDFs you download.
- **Merge Datasets & Dedupe: Combine, Filter, Export CSV & Excel** combines the result rows of several runs.

# Actor input Schema

## `urls` (type: `array`):

Direct links to the files to download (PDF, DOCX, XLSX, CSV, MP3, MP4, ZIP, images and more), one per line. Example: https://example.com/report.pdf. Use only files you have the right to download and reuse.

## `pageUrls` (type: `array`):

Web pages whose linked files you want: the Actor reads <a href>, <source>, <embed>, <object>, <audio> and <video> links and downloads the ones that end in a file extension from 'Only these extensions' (or a built-in list of document, archive, audio, video and image types). Example: https://example.com/reports. The page's robots.txt is respected; login-walled social sites are not supported.

## `maxFiles` (type: `integer`):

Most URLs processed in this run (listed and extracted from pages together). URLs beyond this are not fetched and not charged.

## `maxFileSizeMb` (type: `number`):

Files larger than this are skipped, checked from the headers and again while downloading. 1 MB = 1,000,000 bytes.

## `allowedExtensions` (type: `array`):

Optional. Keep only files with these extensions, for example pdf, docx, csv. Leave empty to keep every type that is not blocked. The extension comes from the file's bytes, then the URL, the Content-Disposition name and the server's content type. For pageUrls it also decides which links are followed.

## `blockedExtensions` (type: `array`):

Files with these extensions are skipped (programs, installers, scripts and web pages by default). Blocked wins over 'Only these extensions'. Web pages (HTML) are never saved, whatever this list says, and a program or script you unblock is stored as .bin, never as .exe or .js.

## `dedupe` (type: `boolean`):

Files with identical content (SHA-256) are delivered once; later copies get status duplicate and are not charged.

## `zip` (type: `boolean`):

Also save all delivered files in one files.zip in the key-value store (with a manifest.csv), with a public link in the SUMMARY record. The ZIP itself is free.

## `zipMaxMb` (type: `number`):

Files that would push the ZIP over this size are still saved and listed, but left out of the ZIP.

## `keepOriginalNames` (type: `boolean`):

Off: ZIP entries are named 0001-name.ext (numbered, so names never clash). On: just name.ext, taken from the URL or Content-Disposition and sanitised (no folders, only letters, digits, dot, dash, underscore); a counter is added if two files share a name.

## `outputStoreName` (type: `string`):

Optional key-value store name. Named stores are kept until you delete them, while a run's own store expires after your plan's retention period.

## `concurrency` (type: `integer`):

How many files are downloaded at the same time, across all hosts.

## `perHostConcurrency` (type: `integer`):

At most this many downloads run at once on the same website, so a long list does not hammer one server.

## `perHostDelayMs` (type: `integer`):

Minimum time between two requests to the same website.

## `maxRetries` (type: `integer`):

How often a download is retried after a network error, timeout or HTTP 429/5xx, with growing waits (Retry-After is honored). 401, 403 and 404 are never retried.

## `timeoutSecs` (type: `integer`):

Time allowed for each download attempt, counted from when it gets its turn at its host (see the per-host delay).

## `maxPages` (type: `integer`):

How many pageUrls pages are read. Further pages get a skipped row.

## `proxyConfiguration` (type: `object`):

Optional. Send downloads through Apify Proxy or your own proxies. Usually not needed. Each retry uses a new session (new IP).

## Actor input object example

```json
{
  "urls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    "https://people.sc.fsu.edu/~jburkardt/data/csv/addresses.csv",
    "https://www.w3.org/Icons/w3c_main.png",
    "https://jsonplaceholder.typicode.com/todos/1"
  ],
  "maxFiles": 100,
  "maxFileSizeMb": 100,
  "blockedExtensions": [
    "exe",
    "msi",
    "bat",
    "cmd",
    "com",
    "scr",
    "ps1",
    "sh",
    "dll",
    "jar",
    "vbs",
    "hta",
    "js",
    "mjs",
    "html",
    "htm",
    "xhtml",
    "shtml",
    "apk",
    "dmg",
    "pkg",
    "deb",
    "rpm"
  ],
  "dedupe": true,
  "zip": true,
  "zipMaxMb": 100,
  "keepOriginalNames": false,
  "concurrency": 5,
  "perHostConcurrency": 3,
  "perHostDelayMs": 250,
  "maxRetries": 2,
  "timeoutSecs": 120,
  "maxPages": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One row per URL (the default dataset), as JSON, CSV, Excel and more.

## `records` (type: `string`):

Key-value store records: the downloaded files, the run summary (SUMMARY) and the ZIP (files.zip) if requested.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
        "https://people.sc.fsu.edu/~jburkardt/data/csv/addresses.csv",
        "https://www.w3.org/Icons/w3c_main.png",
        "https://jsonplaceholder.typicode.com/todos/1"
    ],
    "blockedExtensions": [
        "exe",
        "msi",
        "bat",
        "cmd",
        "com",
        "scr",
        "ps1",
        "sh",
        "dll",
        "jar",
        "vbs",
        "hta",
        "js",
        "mjs",
        "html",
        "htm",
        "xhtml",
        "shtml",
        "apk",
        "dmg",
        "pkg",
        "deb",
        "rpm"
    ],
    "zip": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("data-done-right/bulk-file-downloader").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "urls": [
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
        "https://people.sc.fsu.edu/~jburkardt/data/csv/addresses.csv",
        "https://www.w3.org/Icons/w3c_main.png",
        "https://jsonplaceholder.typicode.com/todos/1",
    ],
    "blockedExtensions": [
        "exe",
        "msi",
        "bat",
        "cmd",
        "com",
        "scr",
        "ps1",
        "sh",
        "dll",
        "jar",
        "vbs",
        "hta",
        "js",
        "mjs",
        "html",
        "htm",
        "xhtml",
        "shtml",
        "apk",
        "dmg",
        "pkg",
        "deb",
        "rpm",
    ],
    "zip": True,
}

# Run the Actor and wait for it to finish
run = client.actor("data-done-right/bulk-file-downloader").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    "https://people.sc.fsu.edu/~jburkardt/data/csv/addresses.csv",
    "https://www.w3.org/Icons/w3c_main.png",
    "https://jsonplaceholder.typicode.com/todos/1"
  ],
  "blockedExtensions": [
    "exe",
    "msi",
    "bat",
    "cmd",
    "com",
    "scr",
    "ps1",
    "sh",
    "dll",
    "jar",
    "vbs",
    "hta",
    "js",
    "mjs",
    "html",
    "htm",
    "xhtml",
    "shtml",
    "apk",
    "dmg",
    "pkg",
    "deb",
    "rpm"
  ],
  "zip": true
}' |
apify call data-done-right/bulk-file-downloader --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,data-done-right/bulk-file-downloader"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/lwysBQ444609VBO3a/builds/yJwoUtztf7k6SBiHq/openapi.json
