# Erome Scraper & Video Downloader (`1codefort/erome-scraper-downloader`) Actor

Scrape and download Erome albums, profiles and search. One row per video or image with the direct CDN link, a working download URL, duration, resolution, real file size, a thumbnail for every video, tags, views and full uploader details.

- **URL**: https://apify.com/1codefort/erome-scraper-downloader.md
- **Developed by:** [1codefort](https://apify.com/1codefort) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 results

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Erome Scraper & Video Downloader

Scrape Erome albums, creator profiles and keyword searches — and, when you ask for it, download the actual files. One clean row per media item, with everything the other Erome actors leave out.

Feed it **album links, profile links, usernames or a keyword in the same run** — no stitching separate runs together afterwards.

***

### Why this one

Most Erome actors hand you a list of `vNN.erome.com/..._720p.mp4` links and stop there. Those links **answer `403 Forbidden`** the moment your own script fetches them, because Erome's CDN requires a `Referer: https://www.erome.com/` header. A dataset of dead links is not a product.

This actor closes that gap:

| | Typical Erome actors | This actor |
|---|---|---|
| Direct CDN links | yes | yes |
| **Working download link (no special headers needed)** | no | **yes** — a pre-signed, permanent public URL per file, stored in the run |
| **Ready-to-run `curl` + exact headers for the CDN link** | no | **yes** — `curlCommand`, `requestHeaders` |
| **Real file size per item** | rarely | **yes** — true bytes + readable form |
| **Thumbnail for *every* video** | only the ones Erome renders | **yes** — derived from the CDN when Erome lazy-loads it |
| Duration in seconds + `MM:SS` | claimed by some | **yes**, from the page itself |
| True resolution + quality label | no (`res='720' label='HD'`) | **yes** — `qualityRes`, `qualityLabel` |
| Image pixel dimensions | no | **yes** — `width`, `height` |
| Album tags, views, uploader avatar | partial | **yes** |
| One row per media item (no second lookup) | yes | **yes** |
| Album-only output mode (cheap catalogue runs) | no | **yes** |
| **Real ZIP per album** | advertised, usually a stub | **yes**, actually implemented |
| Error rows charged | often | **never** — only successful rows are charged |

Honest note on quality selection: Erome publishes **a single 720p (HD) rendition** per video. Actors that offer a "1080p / 480p / 360p" picker are choosing between options that do not exist — those URLs return `410 Gone`. This actor accepts `preferredQuality` for drop-in compatibility, resolves it against the renditions actually exposed, and always reports the real resolution it returned.

***

### Three ways in, one run

```json
{
  "startUrls": [
    "https://www.erome.com/a/9epdXFu0",
    "https://www.erome.com/ScandalouzBitchezTripleSIXXX",
    "someotherusername"
  ],
  "searchQuery": "amateur"
}
```

- **Album link** (`https://www.erome.com/a/XXXXXXXX`) → that album's media.
- **Profile link or bare username** → every album from that creator (capped by `maxAlbumsPerProfile`).
- **Keyword** (`searchQuery`) → Erome search results → their albums (capped by `maxSearchPages` / `maxAlbumsPerSearch`).

Every entry is classified automatically. A bare word that turns out not to be a creator is **retried as a keyword** instead of failing the run.

***

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `startUrls` | array | `["https://www.erome.com/a/9epdXFu0"]` | Album links, profile links or plain usernames. Detected automatically. |
| `urlText` | string | – | Copy/paste fallback: one link or username per line. |
| `usernames` | array | – | Creator usernames to crawl, one entry each. |
| `searchQuery` | string | – | Keyword to search on Erome. Usable on its own or next to the links. |
| `mediaType` | select | `all` | `all` · `video` · `image`. |
| `outputMode` | select | `media` | `media` = one row per item · `albums` = one row per album · `both`. |
| `maxResults` | integer | `1000` | Hard cap on media items for the whole run — this is your cost cap. |
| `maxAlbumsPerProfile` | integer | `50` | Albums pulled per creator. |
| `maxSearchPages` | integer | `1` | Erome result pages to walk (~36 albums per page). |
| `maxAlbumsPerSearch` | integer | `50` | Albums scraped from a keyword. |
| `downloadFiles` | boolean | `false` | Download the real files into the run and give every row a permanent `downloadUrl`. |
| `createZip` | boolean | `false` | Also bundle each album into a single ZIP. Requires `downloadFiles`. |
| `preferredQuality` | select | `best` | Resolved against the renditions Erome really exposes. |
| `includeFileSize` | boolean | `true` | True file size via a 1-byte range request. Turn off for the fastest run. |
| `includeErrorRows` | boolean | `false` | Off by default because the platform bills every dataset item, so a failed link would show up as a paid row. Failures are reported in the log and in the run's `SUMMARY` record instead. |
| `concurrency` | integer | `8` | Albums scraped in parallel. |
| `maxRequestsPerSecond` | integer | `8` | Politeness limit (0 = unlimited). |
| `maxZipSizeMb` | integer | `2048` | Skip a ZIP bigger than this. |
| `proxyConfiguration` | proxy | – | Optional. Only needed for a specific country or after a rate limit — this actor already retries refused requests through it. |

***

### Output

One row per media item. Example (fields trimmed for width):

```json
{
  "type": "video",
  "mediaId": "1bx9HTKu",
  "position": 2,
  "title": "Album title",
  "url": "https://v46.erome.com/9037/9epdXFu0/1bx9HTKu_720p.mp4",
  "directUrlRequiresReferer": true,
  "requestHeaders": {
    "Referer": "https://www.erome.com/",
    "User-Agent": "Mozilla/5.0 ..."
  },
  "curlCommand": "curl -L -H 'Referer: https://www.erome.com/' -o 'file.mp4' '...'",
  "downloadUrl": "https://api.apify.com/v2/key-value-stores/STORE/records/media/9epdXFu0/...",
  "fileSizeBytes": 2929225,
  "sizeFormatted": "2.8MB",
  "thumbnailUrl": "https://s46.erome.com/9037/9epdXFu0/1bx9HTKu.jpg",
  "durationSec": 22,
  "durationText": "00:22",
  "format": "mp4",
  "qualityLabel": "HD",
  "qualityRes": 720,
  "availableFormats": [{ "url": "...", "type": "video/mp4", "qualityLabel": "HD", "qualityRes": 720, "sizeBytes": 2929225 }],
  "albumId": "9epdXFu0",
  "albumUrl": "https://www.erome.com/a/9epdXFu0",
  "albumTitle": "Album title",
  "uploader": "creator",
  "uploaderUrl": "https://www.erome.com/creator",
  "uploaderAvatarUrl": "https://avatar.erome.com/...",
  "tags": ["Asian", "Amateur", "Teen"],
  "viewsText": "131,4K",
  "views": 131400,
  "albumMediaCount": 5,
  "albumVideoCount": 3,
  "albumImageCount": 2,
  "sourceUrl": "https://www.erome.com/a/9epdXFu0",
  "sourceType": "album",
  "searchQuery": null,
  "inputUrl": "https://www.erome.com/a/9epdXFu0",
  "scrapedAt": "2026-09-13T03:17:41.494Z",
  "status": "ok",
  "errorMessage": null
}
```

Images carry the same shape with `"type": "image"`, plus the real `width` and `height`.

Album rows (`outputMode: albums`) carry the album metadata plus `previewImage` and — with `createZip` — `zipFileUrl`.

The run also writes a `SUMMARY` record to the key-value store with album/media counts, error counts, files downloaded and total bytes.

***

### Downloading the files

`downloadFiles: true` streams every file into the run's key-value store and puts a **pre-signed public URL** on each row:

```
https://api.apify.com/v2/key-value-stores/<store>/records/media-<albumId>-<position>-<mediaId>.mp4?signature=XXXXXXXX
```

This is worth explaining, because it is the single most common way an Erome downloader ships broken. The "obvious" URL for a stored file is

```
https://api.apify.com/v2/key-value-stores/<store>/records/<key>          ← HTTP 403
```

and it answers **403 insufficient-permissions** to anyone without an Apify token: run storages inherit the owner's *restricted* general access. A dataset full of those links looks perfect and downloads nothing. Apify signs a record URL with an HMAC signature, and a signed record link is **permanent** ("it stays valid as long as the record exists"), so it needs no token, no cookie and no header. Every row says which kind of link it got, in `downloadUrlPublic` (verified with a plain `fetch`: `200 video/mp4` with the exact byte count).

Drop those links in a browser, `ffmpeg`, a NAS, or an archive script. With `createZip: true` each album is also packed into one streaming ZIP (nothing is buffered in memory, so a 3 GB album is fine) and the ZIP link is signed the same way.

Two honest caveats:

- "Permanent" means *as long as both the record and the run's storage exist*. Apify deletes run storages after your account's data-retention period, so treat the links as durable for that window — they are not an archive. For a real archive, download the files to your own storage.
- Because the actor only charges for files it actually stored, a download that fails costs you nothing.

***

### Pricing

Pay per event — you pay for what you get:

| Event | Price | When |
|---|---|---|
| Actor start | **$0.0002** | once per run |
| result | **$0.001** | per row written (media item or album row) — **$1.00 per 1,000 results** |
| file stored | **$0.01 per 10 MB** (minimum 1) | per file actually downloaded and stored — roughly **$1 per GB you actually get** |

Volume discounts apply automatically from Bronze up (down to $0.0007 per result). **Failures are never billed** — error rows stay out of the dataset by default, so they are not charged.

#### How this compares

| Actor | Price per 1,000 results |
|---|---|
| **this one** | **$0.70** at the best volume tier · **$1.00** on the free tier |
| solidcode/erome-scraper-video-downloader | $1.70 at the best tier · $2.00 on the free tier |
| vnx0/erome-downloader | $5.00 + $0.10 per run |
| serpxxx/erome-bulk-video-downloader | $10.00 |
| serpxxx/erome-downloader, pornvidsdownload/how-to-download-erome-videos | no runtime at all |

That is **41% below the cheapest competing Erome actor** on the free tier and 59% below it at volume, while returning more fields, a working download link and a real ZIP.

Why it can be that cheap: Erome answers from Apify's own infrastructure, so no proxy is needed for the normal path, and the actor streams instead of buffering, so 1 GB of memory is enough even for large albums. Metadata runs cost the platform about **$0.015 per 1,000 rows** (compute $0.20 per GB-hour, dataset writes $0.005 per 1,000). Stored files cost about **$0.42 per GB** (egress plus a week of storage), which is why the stored-file event is priced per 10 MB rather than per file — a 300 MB clip costs more to host than a 3 MB one, and it should pay for itself rather than being subsidised by cheap clips.

Cost examples:

- 1,000 results, metadata only → **$1.00**
- 1 album of 5 items → about **$0.0002 + $0.005**
- 1 album of 20 clips, files downloaded (~120 MB) → **$0.02** of rows + **$0.12** of storage
- catalogue run: `outputMode: albums` + `includeFileSize: false` → $0.001 per album and no album page is ever opened

***

### Use it from anywhere

- **API** — every run is a REST call, so it drops into any backend.
- **MCP server** — plug it into an AI agent as a tool.
- **Integrations** — Google Sheets, n8n, Make, Zapier, Slack, webhooks.
- **Schedules** — keep a watchlist of profiles or a keyword fresh on a timer.

***

### Notes, limits and fair use

- **Direct CDN links are short-lived.** Erome rotates the tokens behind them — that is exactly why this actor returns fresh ones per run. `downloadFiles` gives you links that do not expire.
- **One 720p rendition per video** (see above). Nothing in the output pretends otherwise.
- **Private content is out of scope.** The actor reads public album, profile and search pages only; it does not log in, bypass paywalls, or touch private albums.
- **You are responsible for what you download.** Use it for content you own, content you have permission to access, or content you are legally allowed to archive. Respect Erome's terms and the rights of the people in the media.
- **Failures never cost you a row.** Because the platform bills for every item written to the dataset, error rows are kept *out* of the dataset by default (they go to the log and to the run's `SUMMARY` record). Set `includeErrorRows: true` if you want them in the dataset — knowing those rows are then charged like any other. Either way a bad link in a batch of 500 never kills the run.

***

### Quick recipes

**One album, metadata only**

```json
{ "startUrls": ["https://www.erome.com/a/9epdXFu0"] }
```

**Whole creator, videos only, files downloaded, max 25 albums**

```json
{ "startUrls": ["somecreator"], "mediaType": "video", "downloadFiles": true, "createZip": true, "maxAlbumsPerProfile": 25 }
```

**Keyword catalogue, 3 result pages, album rows only**

```json
{ "searchQuery": "amateur", "maxSearchPages": 3, "outputMode": "albums" }
```

**Refresh a list of albums on a schedule, images only, cheap**

```json
{ "urlText": "https://www.erome.com/a/XXXXXXXX\nhttps://www.erome.com/a/YYYYYYYY", "mediaType": "image", "includeFileSize": false }
```

# Actor input Schema

## `startUrls` (type: `array`):

Paste one or more Erome album links (https://www.erome.com/a/XXXXXXXX), user profile links (https://www.erome.com/username) or plain usernames — one per entry. Each entry is detected automatically: album links pull that album's media, profile links and bare usernames pull every album of that creator. Leave empty if you only want to search. Plain strings are also what the API accepts, so this input is a drop-in replacement for the other Erome actors.

## `urlText` (type: `string`):

Optional fallback for copy/paste batches: one album link, profile link or username per line.

## `usernames` (type: `array`):

Optional. Bare Erome usernames to crawl, one entry per creator. Same as passing usernames inside the links field, but easier to build from a spreadsheet.

## `searchQuery` (type: `string`):

Optional. A keyword to search on Erome; matching albums are collected and scraped. Use it on its own or alongside the links above.

## `mediaType` (type: `string`):

Which media to include from each album.

## `outputMode` (type: `string`):

One row per media item (default), one row per album (cheap catalogue runs — no album page is opened), or both.

## `maxResults` (type: `integer`):

The most media items (videos plus images) to return across every album, profile and search. Each media item is one result row, so this caps your cost.

## `maxAlbumsPerProfile` (type: `integer`):

For usernames and profile links: the most albums to pull from a single creator. Ignored for direct album links.

## `maxSearchPages` (type: `integer`):

For a keyword search: how many Erome result pages to walk (about 36 albums per page).

## `maxAlbumsPerSearch` (type: `integer`):

For a keyword search: the most matching albums to scrape from the keyword.

## `downloadFiles` (type: `boolean`):

When on, the real video and image files are downloaded during the run and stored in this run's key-value store. Each row then gets a permanent, public downloadUrl that works without any special header. When off (default) you get the direct CDN links plus the exact headers and a ready-to-run curl command, which is faster and cheaper. Billed as 'file stored': one event per 10 MB, minimum one (roughly $1 per GB actually downloaded).

## `createZip` (type: `boolean`):

Requires 'Download the actual files'. Also packs every downloaded file of an album into a single ZIP stored in the run's key-value store, and puts its link on the album rows.

## `preferredQuality` (type: `string`):

Quality preference for videos. Erome currently publishes a single 720p (HD) rendition per video, so every value resolves to the best available rendition; the actual resolution is always reported in qualityRes / qualityLabel and in availableFormats.

## `includeFileSize` (type: `boolean`):

Adds the true size of each media file (in bytes, plus a readable form) by asking the CDN. Costs one very cheap request per file. Turn off for the fastest possible metadata run.

## `includeErrorRows` (type: `boolean`):

Off by default because the platform bills every item written to the dataset — a failed link showing up as a paid row is not fair. With this off, failures are reported in the actor log and in the run's SUMMARY record (key-value store) instead. Turn it on if you prefer errors to be visible in the dataset itself, and accept that those rows are charged like any other row.

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

How many albums are scraped at the same time. Raise it for big catalogues, lower it if a run gets rate-limited.

## `maxRequestsPerSecond` (type: `integer`):

Politeness limit applied to every request this actor makes to Erome. 0 disables the limit.

## `maxZipSizeMb` (type: `integer`):

Skip the per-album ZIP when the album is bigger than this, so a huge album cannot blow up the run's storage.

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

Optional. Erome answers fine from Apify's own infrastructure, so this is only needed if you want a specific country or you hit a rate limit. If a page request is ever refused, the actor retries it through this proxy automatically. Files are never downloaded through the proxy unless you explicitly allow it below, because bandwidth-heavy proxy traffic is expensive.

## `useProxyForDownloads` (type: `boolean`):

Off by default on purpose: proxy traffic is billed per GB and can cost many times more than the price of the stored-file event. Turn it on only if Erome is refusing your downloads from Apify's IPs and you accept that proxy cost.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.erome.com/a/9epdXFu0"
  ],
  "mediaType": "all",
  "outputMode": "media",
  "maxResults": 1000,
  "maxAlbumsPerProfile": 50,
  "maxSearchPages": 1,
  "maxAlbumsPerSearch": 50,
  "downloadFiles": false,
  "createZip": false,
  "preferredQuality": "best",
  "includeFileSize": true,
  "includeErrorRows": false,
  "concurrency": 8,
  "maxRequestsPerSecond": 8,
  "maxZipSizeMb": 2048,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "useProxyForDownloads": false
}
```

# Actor output Schema

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

The default dataset: one row per media item (type video or image) with the direct CDN link, the signed download URL, duration, resolution, real file size, a thumbnail, album tags, views and uploader details. In album output mode it holds one row per album instead.

## `files` (type: `string`):

The run's key-value store: every media file actually downloaded (key media-<albumId>-<position>-<mediaId>.<ext>) and one ZIP per album (key zip-<albumId>.zip). Present only when 'Download the actual files' is enabled. The matching permanent public URLs are in the dataset rows (downloadUrl, zipFileUrl).

## `runSummary` (type: `string`):

A SUMMARY record with the counters for the run: albums scraped, dataset rows, files downloaded, ZIPs created, total bytes, task errors and the custom events charged.

# 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 = {
    "startUrls": [
        "https://www.erome.com/a/9epdXFu0"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("1codefort/erome-scraper-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 = { "startUrls": ["https://www.erome.com/a/9epdXFu0"] }

# Run the Actor and wait for it to finish
run = client.actor("1codefort/erome-scraper-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 '{
  "startUrls": [
    "https://www.erome.com/a/9epdXFu0"
  ]
}' |
apify call 1codefort/erome-scraper-downloader --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,1codefort/erome-scraper-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/7PeECO3xZQvdZWEfH/builds/xBZJf5NqZo724ikSC/openapi.json
