# Vjav Video Downloader (`abotapi/vjav-video-downloader`) Actor

Download videos from VJAV.com with support for multiple URLs, concurrent downloads, and automatic file management

- **URL**: https://apify.com/abotapi/vjav-video-downloader.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Videos, Other
- **Stats:** 58 total users, 0 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 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?

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

## VJAV Video Downloader

This Apify actor allows you to efficiently download videos from vjav.com, with support for concurrent downloads, multiple URLs, and automatic file management.

### **AGE RESTRICTION NOTICE**

This tool interacts with an adult content platform that contains explicit material. By using this actor, you confirm that:

- You are at least 18 years of age (or the legal age of majority in your jurisdiction)
- You are legally permitted to access adult content in your location
- You understand and accept the nature of the content being processed
  This actor is intended for legitimate research, analysis, and content management purposes only. Please ensure compliance with all applicable laws and regulations in your jurisdiction.

### Features

- **Multi-threaded downloads**: Process up to 10 URLs concurrently
- **Batch processing**: Submit multiple URLs in a single run (up to 10)
- **Smart URL detection**: Automatically finds the highest quality download links
- **Progress tracking**: Visual progress bars with download speed and ETA
- **Efficient storage**: Automatically cleans up local files after upload to Apify storage
- **Proxy support**: Optional proxy configuration
- **Detailed logging**: Comprehensive logs for tracking download progress and issues
- **Structured output**: JSON response with download URLs and video information

### Input Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `urls` | Array | List of VJAV video URLs to process (maximum 10) |
| `maxConcurrent` | Integer | Maximum number of concurrent downloads (1-10, default: 3) |
| `proxy` | String | Optional proxy URL in format `host:port` or `user:pass@host:port` |
| `downloadFolder` | String | Folder to temporarily store downloaded videos (default: ./downloaded-videos) |
| `resumeFromRunId` | String | Skip URLs already downloaded successfully by a previous run/dataset. See [Resume & recurring updates](#resume--recurring-updates). |
| `incrementalMode` | Boolean | Track each video across runs and tag rows NEW/UPDATED/UNCHANGED/REAPPEARED. Default `false`. |
| `stateKey` | String | Optional name for an isolated incremental-mode watchlist. |
| `emitUnchanged` | Boolean | Push UNCHANGED rows too instead of suppressing them. Default `false`. |
| `emitExpired` | Boolean | Present for consistency with Apify's other incremental-mode actors; has **no effect** on this actor. See [Resume & recurring updates](#resume--recurring-updates). |

### Example Input

```json
{
  "urls": [
    "https://vjav.com/videos/890393/amazing-xxx-video-youve-seen/",
    "https://vjav.com/videos/890713/two-studs-enjoy-a-fellatio-from-horny-teenager-18-in-an-uncensored-xxx-jav-with-maomi-nagasawa-and-nagasawa-maomi/"
  ],
  "maxConcurrent": 3,
  "proxy": "103.214.9.13:3128"
}
```

### Output

The actor produces a structured JSON output with information about each processed video and direct download links to the files in Apify's key-value store.

#### Example Output

```json
{
  "processedUrls": 3,
  "successfulDownloads": 2,
  "videos": [
    {
      "videoId": "888399",
      "title": "Incredible XXXX Video Red Head XXXXX Ever Seen",
      "downloadUrl": "https://api.apify.com/v2/key-value-stores/abcd1234/records/888399-incredible-xxxx-video-red-head-xxxx-ever-seen.mp4",
      "success": true
    },
    {
      "videoId": "889000",
      "title": "XXXX Seduces Older Man For XXXX XXXX",
      "downloadUrl": "https://api.apify.com/v2/key-value-stores/abcd1234/records/889000-xxxxx-seduces-older-man-for-xxxx-xxxx.mp4",
      "success": true
    },
    {
      "videoId": "890000",
      "title": "Amateur Couple Homemade Video",
      "error": "Failed to download file: HTTP 404",
      "success": false
    }
  ]
}
```

### Resume & recurring updates

This actor is URL-in, file-out: the only thing that determines what gets processed is the `urls` list you paste in. That shape makes two of Apify's usual recurring-monitoring features meaningful, and one of them fundamentally not.

**`resumeFromRunId`** -- paste a previous run ID or dataset ID and this run skips any URL whose video was already downloaded successfully, so continuing a batch (or re-running after a partial failure) doesn't re-download and re-charge for videos you already have. Works whether or not Incremental mode is on.

**`incrementalMode`** -- when on, the actor remembers every video it has processed (by video ID, not by which URLs happened to be pasted alongside it) and tags each dataset row `changeType`: `NEW` (first time seen), `UPDATED` (title or download link changed since last time), `UNCHANGED` (nothing changed -- suppressed from the dataset unless `emitUnchanged` is on, so a stable video costs zero dataset items on a repeat run), or `REAPPEARED`. Leave `stateKey` blank to track every video you've ever checked in one shared watchlist across runs, regardless of what other URLs are in any given run's `urls` -- this is deliberate: `urls` is an ad hoc per-run selection here, not a saved search, so it is not part of the tracking key. Set `stateKey` to your own text if you want a separate, isolated watchlist (e.g. one per project).

**`emitExpired` has no effect on this actor.** EXPIRED means "this item was tracked before and the site no longer has it" -- and that can only be inferred from a run that provably scanned an entire site-side listing to its natural end. This actor has no listing or search surface; its only input is whatever URLs you paste into one run. A video absent from this run's `urls` was not necessarily removed from vjav.com -- you simply may not have pasted it this time. Marking it EXPIRED would fabricate a row and incorrectly bill you for it, so this actor's EXPIRED gate always refuses, unconditionally, regardless of this toggle. The field is kept only so this actor's input shape matches Apify's other incremental-mode actors.

**Default behavior is unchanged.** With `incrementalMode` off (the default), the actor pushes exactly one dataset item per run -- the same summary object shown in [Example Output](#example-output) -- with no `changeType` field anywhere. Turning `incrementalMode` on switches the dataset to one row per video (matching the fields in [Results and Data Export](#results-and-data-export) below) so each video's change status can be reported individually.

#### What counts as "changed"

Measured live by fetching the same video's page twice, ~20-26s apart (see `probes/probe_volatility_2026_07_30.py`): the title stayed identical every time, but the signed download URL vjav.com issues (`/get_file/.../<id>_hq.mp4/?d=...&br=...&ti=...`) contains a token and a `ti=` timestamp that change on **every single fetch**, even when the video itself hasn't changed. This actor normalizes that token/timestamp away before comparing, so re-checking the same unmodified video never falsely reports UPDATED. A genuine change to the title or to the video file/quality is still reported.

Only two things are tracked per video: its **title** and its **resolved download link** (`selectedUrl`, shown in each incremental row so the field driving `changedFields` is one you can actually inspect). Other candidate links the actor may come across alongside the real download target are deliberately **not** tracked: they can vary for reasons unrelated to the video itself rather than anything changing on vjav.com -- so including them in change detection previously caused a video to be falsely reported as UPDATED on a re-check where nothing had actually changed. Re-measured across 3 different videos, 2 checks each (see `probes/probe_videourls_membership_2026_07_30.py`): title stayed identical for all 3, and the resolved download link was stable across checks.

### Results and Data Export

During the run, the actor stores results into a dataset. With `incrementalMode` off (default), the actor pushes one summary item per run, shaped as in [Example Output](#example-output). With `incrementalMode` on, each processed video becomes a separate dataset item instead, additionally tagged with `changeType`, `changedFields`, `firstSeenAt`, `lastSeenAt`, and `selectedUrl` (the resolved vjav.com download link this run found -- the field that drives `changedFields: ["selectedUrl"]` when it changes).

You can access and export these results in several ways:

#### Dataset Access

The actor's dataset contains detailed information about each processed video, including:

- Video metadata (ID, title)
- Download status and errors (if any)
- URLs for the downloaded files
- Processing statistics

#### Apify API Integration

You can manage the results in any language (Python, PHP, Node JS/NPM). See the [Apify API reference](https://docs.apify.com/api) to learn more about getting results from this VJAV Video Downloader actor.

#### Exporting Data

You can also export the dataset in various formats:

1. **Via Apify Console**:
   - Go to the **Storage** → **Datasets** section
   - Find your dataset and click on it
   - Use the **Export** button to download as JSON, CSV, Excel, etc.

2. **Via API**:
   ```
   https://api.apify.com/v2/datasets/{DATASET_ID}/items?token={YOUR_API_TOKEN}&format=json
   ```

#### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape — the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record — not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Proxy Configuration

For reliable access to vjav.com, consider using a proxy:

1. You can use your own proxy in the input parameters
2. Or subscribe to Apify Proxy for access to residential and datacenter proxies

### Storage and Retention

- Downloaded videos are stored in Apify's key-value store
- By default, files in unnamed key-value stores will expire after 7 days
- To retain files longer, name your key-value store when you start the actor run

# Actor input Schema

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

List of VJAV video URLs to download (maximum 10)

## `proxy` (type: `string`):

Optional proxy URL in format host:port or user:pass@host:port

## `maxConcurrent` (type: `integer`):

Maximum number of concurrent downloads (1-10)

## `downloadFolder` (type: `string`):

Folder to temporarily store downloaded videos (default: ./downloaded-videos)

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID here to skip URLs it already downloaded successfully, so a re-run does not re-download (and re-charge for) them. Independent of Incremental mode below -- works with it off too.

## `incrementalMode` (type: `boolean`):

When on, the actor remembers each video it has processed (by video ID) and tags every row with changeType: NEW / UPDATED / UNCHANGED / REAPPEARED, so re-running this actor later against the same video(s) reports only what changed. UNCHANGED rows are not pushed to the dataset (see Emit unchanged rows). Off by default: output is a single summary item exactly as before, with no changeType field. EXPIRED never fires for this actor -- see Emit expired rows below.

## `stateKey` (type: `string`):

Only used when Incremental mode is on. Leave blank to use one shared watchlist for all your incremental runs of this actor (tracking is by video ID, not by which URLs you happen to paste in a given run). Set your own text here to keep a separate, isolated watchlist -- e.g. one per project.

## `emitUnchanged` (type: `boolean`):

Only used when Incremental mode is on. When on, videos classified UNCHANGED are also pushed to the dataset (and billed as normal dataset items) instead of being suppressed. Off by default.

## `emitExpired` (type: `boolean`):

Only used when Incremental mode is on. Kept for consistency with Apify's other incremental-mode actors, but it has NO EFFECT here: EXPIRED requires proving a complete scan of a site-side listing, and this actor has no listing -- its only input is the URLs you paste into each run, so a video missing from `urls` this run simply was not pasted, not removed from vjav.com. This actor will never emit an EXPIRED row regardless of this setting.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "urls": [
    "https://vjav.com/videos/890393/amazing-xxx-video-youve-seen/"
  ],
  "maxConcurrent": 3,
  "downloadFolder": "./downloaded-videos",
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50
}
```

# 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://vjav.com/videos/890393/amazing-xxx-video-youve-seen/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/vjav-video-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://vjav.com/videos/890393/amazing-xxx-video-youve-seen/"] }

# Run the Actor and wait for it to finish
run = client.actor("abotapi/vjav-video-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://vjav.com/videos/890393/amazing-xxx-video-youve-seen/"
  ]
}' |
apify call abotapi/vjav-video-downloader --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/vjav-video-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/w4nn28tKzrWQy33h0/builds/GgIsiG9rfEljplxgj/openapi.json
