Back to directory
rodrigogs avatar

xvideos

xvideos API library. Read more below about its uses, features, and usage.

Clone repository

git clone https://github.com/rodrigogs/xvideos.git

302

Stars

84

Forks

27

Watchers

BSD-3-Clause

License

xvideos

npm version npm downloads CI codecov CodeQL license

A Node.js library for the xvideos.com API.

Requires Node.js 20+.

Installation

$ npm install @rodrigogs/xvideos

Usage

import xvideos from '@rodrigogs/xvideos';
const xvideos = require('@rodrigogs/xvideos');

(async () => {
  // Retrieve fresh videos from the first page
  const fresh = await xvideos.videos.fresh({ page: 1 });
  // Log details of the retrieved videos
  console.log(fresh.videos); // Array of video objects with properties like url, videoId, title, duration, durationSeconds, thumbnailUrl, profile, watchCount
  console.log(fresh.pagination.page); // Current page number
  console.log(fresh.pagination.pages); // Array of available page numbers
  console.log(fresh.hasNext()); // Check if there is a next page
  console.log(fresh.hasPrevious()); // Check if there is a previous page

  // Retrieve the next page of fresh videos
  const nextPage = await fresh.next();
  // Log details of the next page
  console.log(nextPage.pagination.page); // Updated current page number
  console.log(nextPage.hasNext()); // Check if the next page exists
  console.log(nextPage.hasPrevious()); // Check if the previous page exists

  // Retrieve the previous page of fresh videos
  const previousPage = await fresh.previous();
  // Log details of the previous page
  console.log(previousPage.pagination.page); // Updated current page number
  console.log(previousPage.hasNext()); // Check if the next page exists
  console.log(previousPage.hasPrevious()); // Check if the previous page exists

  // Retrieve detailed information about a specific video
  const detail = await xvideos.videos.details(fresh.videos[0]);
  // Log details of the specific video
  console.log(detail); // Detailed video object with properties like title, videoId, duration, durationSeconds, thumbnailUrls, watchCount, videoType, files, uploadDate, tags, categories

  // Retrieve many detail pages with explicit crawl controls
  const batch = await xvideos.videos.detailsMany(fresh.videos.slice(0, 3), {
    concurrency: 2,
    retries: 1,
    minDelayMs: 250,
  });
  console.log(batch.successes); // Successful detail payloads in input order
  console.log(batch.failures); // Failed inputs with their error
})();

Crawl ergonomics

xvideos.configure({ minRequestIntervalMs, proxyUrl })

Configures process-wide request behavior. Applies to every request the library makes from this process (all videos.* methods, list and detail alike).

import xvideos from '@rodrigogs/xvideos';

xvideos.configure({
  // Minimum spacing between request starts (milliseconds). Keeps the
  // library polite against rate limiters and is shared across every
  // concurrent client in the process.
  minRequestIntervalMs: 250,
  // Route requests through an HTTP(S) proxy — useful when running from a
  // datacenter IP that XVIDEOS blocks (e.g. CI runners).
  proxyUrl: 'http://user:[email protected]:8080',
});

minRequestIntervalMs is shared process-wide: the largest configured interval across all clients wins and cannot be lowered afterwards (per-client options raise it further; resetSharedThrottle is exposed from base.ts for full reset). proxyUrl can also be passed per client via RequestOptions when you do not want a global proxy.

Note for CommonJS consumers: require('@rodrigogs/xvideos').configure(...) returns a Promise (the CJS entry loads the ESM build lazily) — await it or chain .then() before firing requests to guarantee the config is applied first.

Development

npm run build
npm run lint
npm run format
npm run test:unit
npm run test:integration
npm run coverage
npm test

Real-HTML fixtures under test/fixtures/ pin the current site layout. If a fixture test fails after an XVIDEOS layout change, regenerate with scripts/refresh-fixtures.sh and commit the diff.

Migration Notes

Version 3.2 category browsing and crawl ergonomics

This release is additive:

  • videos.category({ category, page }) — category video listings by slug (/c/<slug>), with 404 handling that surfaces an empty listing for unknown categories
  • xvideos.configure({ minRequestIntervalMs, proxyUrl }) — process-wide crawl ergonomics: a shared minimum interval between request starts (rate limiting, shared across all clients) and optional HTTP(S) proxy routing
  • retry backoff now uses exponential backoff with full jitter instead of a fixed linear delay
  • real-HTML fixtures (test/fixtures/) pin the current site layout — parser tests fail on layout changes instead of production code. Regenerate with scripts/refresh-fixtures.sh

Version 3.1 richer list results and crawl ergonomics

This release is additive:

  • list items now include durationSeconds and thumbnailUrl
  • videos.detailsMany() adds ordered batch detail fetching with concurrency, retries, retryDelayMs, and minDelayMs

Version 3.0 field normalization

Some fields were normalized to remove redundant data while keeping all information available:

Previous field New field Notes
videos[].path videos[].videoId Use video.url if you need full link, or rebuild path with /${video.videoId} when required.
videos[].views videos[].watchCount Numeric form for sorting/filtering.
details.image details.thumbnailUrls[0] Primary thumbnail remains available as first item.
details.views details.watchCount Numeric form for analytics and ranking.

New fields added

  • videos[].durationSeconds

  • videos[].thumbnailUrl

  • details.videoId

  • details.durationSeconds

  • details.thumbnailUrls

  • details.watchCount

  • details.voteCount

  • details.ratingPercent

  • details.uploadDate

  • details.description

  • details.contentUrl

  • details.tags

  • details.categories

These changes keep feature parity and add richer metadata from structured page data.

API

Retrieve Dashboard Videos

// Retrieve dashboard videos from the first page
const dashboardList = await xvideos.videos.dashboard({ page: 1 });

// Check if there is a next page of results
console.log(dashboardList.hasNext()); // Outputs: true or false

// Check if there is a previous page of results
console.log(dashboardList.hasPrevious()); // Outputs: true or false

// Refresh the current page of results to get updated data
const refreshedVideos = await dashboardList.refresh();

// Retrieve the next page of dashboard videos if available
const nextVideos = await dashboardList.next();

// Retrieve the previous page of dashboard videos if available
const previousVideos = await dashboardList.previous();

Retrieve Fresh Videos

// Retrieve fresh videos from the first page
const freshList = await xvideos.videos.fresh({ page: 1 });

// Check if there is a next page of results
console.log(freshList.hasNext()); // Outputs: true or false

// Check if there is a previous page of results
console.log(freshList.hasPrevious()); // Outputs: true or false

// Refresh the current page of results to get updated data
const refreshedVideos = await freshList.refresh();

// Retrieve the next page of fresh videos if available
const nextVideos = await freshList.next();

// Retrieve the previous page of fresh videos if available
const previousVideos = await freshList.previous();

Retrieve Best Videos

// Retrieve best videos for a specific year and month, starting from the first page
const bestList = await xvideos.videos.best({ year: '2018', month: '02', page: 1 });

// Check if there is a next page of results
console.log(bestList.hasNext()); // Outputs: true or false

// Check if there is a previous page of results
console.log(bestList.hasPrevious()); // Outputs: true or false

// Refresh the current page of results to get updated data
const refreshedVideos = await bestList.refresh();

// Retrieve the next page of best videos if available
const nextVideos = await bestList.next();

// Retrieve the previous page of best videos if available
const previousVideos = await bestList.previous();

Retrieve Verified Videos

// Retrieve verified videos from the first page
const verifiedList = await xvideos.videos.verified({ page: 1 });

// Check if there is a next page of results
console.log(verifiedList.hasNext()); // Outputs: true or false

// Check if there is a previous page of results
console.log(verifiedList.hasPrevious()); // Outputs: true or false

// Refresh the current page of results to get updated data
const refreshedVideos = await verifiedList.refresh();

// Retrieve the next page of verified videos if available
const nextVideos = await verifiedList.next();

// Retrieve the previous page of verified videos if available
const previousVideos = await verifiedList.previous();

Retrieve Category Videos

// Retrieve videos from a specific category, starting from the first page
const categoryList = await xvideos.videos.category({ category: 'AI-239' });

// Specify a page number
const categoryPage2 = await xvideos.videos.category({
  category: 'Amateur-65',
  page: 2,
});

// Check if there is a next page of results
console.log(categoryList.hasNext()); // Outputs: true or false

// Refresh / navigate like any other list result
const refreshed = await categoryList.refresh();
const nextVideos = await categoryList.next();

The category option is the slug as it appears in category urls (/c/<category>), e.g. 'AI-239' or 'Amateur-65'. Unknown categories return an empty listing instead of throwing.

Retrieve Video Details

// Retrieve detailed information about a specific video using its URL
const details = await xvideos.videos.details({ url: 'https://www.xvideos.com/video36638661/chaturbate_lulacum69_30-05-2018' });

// Log detailed information about the video
console.log(details); // Detailed video object with properties like title, videoId, duration, durationSeconds, thumbnailUrls, watchCount, videoType, files, uploadDate, description, contentUrl, tags, categories, voteCount, ratingPercent

Retrieve Many Video Details

const batch = await xvideos.videos.detailsMany(
  [
    { url: 'https://www.xvideos.com/video123/example' },
    { url: 'https://www.xvideos.com/video456/example' },
  ],
  {
    concurrency: 3,
    retries: 1,
    retryDelayMs: 250,
    minDelayMs: 500,
  },
);

console.log(batch.items); // One entry per input, preserving order
console.log(batch.successes); // Successful detail payloads only
console.log(batch.failures); // Failed requests with input + error

detailsMany() is intended for enrichment and crawling flows where you want explicit control over throughput and retry behavior without making list methods heavy by default.

Filter Videos

// Search for videos using a keyword, and optionally specify a page number
const videos = await xvideos.videos.search({ k: 'threesome' });
// Example with a specific page number
// const videos = await xvideos.videos.search({ k: 'public', page: 5 });

// Check if there is a next page of results
console.log(videos.hasNext()); // Outputs: true or false

// Check if there is a previous page of results
console.log(videos.hasPrevious()); // Outputs: true or false

// Refresh the current page of results to get updated data
const refreshedVideos = await videos.refresh();

// Retrieve the next page of videos if available
const nextVideos = await videos.next();

// Retrieve the previous page of videos if available
const previousVideos = await videos.previous();

// Search for videos with specific parameters
const videos = await xvideos.videos.search({
  page: 2,
  k: 'threesome',
  sort: 'rating',
  datef: 'week',
  durf: '3-10min',
  quality: 'hd'
});

// Log the search results
console.log(videos); // Array of video objects with properties based on the search parameters

Params explanation

Parameter Default Options
page 1 (any positive integer)
k "" (any search keyword)
sort "relevance" "uploaddate", "rating", "length", "views", "random"
datef "all" "today", "week", "month", "3month", "6month", "all"
durf "allduration" "1-3min", "3-10min", "10min_more", "10-20min", "20min_more", "allduration"
quality "all" "hd", "1080P", "all"

License

Licence © Rodrigo Gomes da Silva

Releases

What's Changed

chore(release): v3.2.2 fix: details().categories no longer captures the global nav menu

Validation

npm run build npm run lint npm run test:unit npm publish

What's Changed

chore(release): v3.2.1 docs(changelog): v3.2.1 fix: serialize concurrent throttle reservations; accept underscore category slugs chore: migrate biome linter config to preset (removes d...

What's Changed

chore(release): v3.2.0 feat: category browsing and crawl ergonomics (configure, jitter backoff, fixtures) Update GitHub Actions versions

Validation

npm run build npm run lint npm run...

Apr 26, 2026

Download .zip

What's Changed

add durationSeconds and thumbnailUrl to list results across dashboard, fresh, best, verified, and search add videos.detailsMany() for ordered batch detail fetching with concurrency, re...

Apr 26, 2026

Download .zip

Highlights

Added richer video metadata extraction from structured page data and detail page taxonomy. Normalized redundant fields in list and detail responses. Preserved crawling/navigation capabilit...