# PeerTube Instance Harvester (`datamule/peertube-instance-harvester`) Actor

Point at any PeerTube instance and export its videos, channels and instance metadata from the public /api/v1 REST API. Federated, self-hosted YouTube alternative — one actor over thousands of instances.

- **URL**: https://apify.com/datamule/peertube-instance-harvester.md
- **Developed by:** [Datamule](https://apify.com/datamule) (community)
- **Categories:** Social media, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.35 / 1,000 videos

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/platform/actors/running/actors-in-store#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

## PeerTube Instance Harvester

**Point at any PeerTube instance and export its videos, channels and instance metadata to a
structured dataset — from the public `/api/v1` REST API.**

[PeerTube](https://joinpeertube.org/) is the ActivityPub-federated, self-hosted alternative to
YouTube. Every instance — [framatube.org](https://framatube.org),
[makertube.net](https://makertube.net), [tube.tchncs.de](https://tube.tchncs.de) and thousands
more — exposes the **identical public `/api/v1` REST API** with no auth for public content. This
one actor works against **all of them**: no per-instance scraper, no host enumeration. Give it an
instance URL and it turns that instance's video timeline, search results, channel directory, or
server metadata into clean rows.

### Why this actor

Most "PeerTube" tools on the market clone a single instance or scrape the web UI. This is a
**generic runner over the protocol** — the same field is read by key presence on every instance,
so an instance that omits an optional field yields `null` for that column instead of breaking.
One actor, thousands of federated video sources.

It also captures **federation correctly**: a video listed on `framatube.org` may actually live on
`tube.tchncs.de`. The real origin `host` is captured on every row, alongside the instance you
queried (`_source`).

### Modes

The run mode is chosen from your input:

| Mode | How to trigger | What you get |
|------|----------------|--------------|
| **Videos** (default) | `mode: videos` (or just an instance URL) | One row per video from `/api/v1/videos`. Add a `search` term to use `/api/v1/search/videos`. Sort with `sort` (default newest first). |
| **Channels** | `mode: channels` | One row per channel from `/api/v1/video-channels` (name, host, followers, owner account). |
| **Discovery** | `mode: discovery` or `listOnly: true` | One row describing the instance: name, server version, signup policy, short description, total video count. A cheap way to inspect an instance. |

### Input

| Field | Type | Description |
|-------|------|-------------|
| `instanceUrl` | string (required) | Root of a PeerTube instance, e.g. `https://framatube.org`. The `/api/v1` path is appended for you. |
| `mode` | string | `videos` (default), `channels`, or `discovery`. |
| `search` | string | Videos mode: free-text search across the instance's federated index. |
| `sort` | string | Videos mode: sort order (default `-publishedAt`). Also accepts `-views`, `-likes`, `-trending`, `name`, … |
| `includeNsfw` | boolean | Videos mode: include NSFW videos (default `false`). |
| `listOnly` | boolean | Shortcut for discovery mode. |
| `maxRecords` | integer | Global cap on rows emitted (deterministic sample). |
| `bearer` | string (secret) | Optional token for a private / rate-limited instance. Never required for public content, never logged. |
| `extraHeaders` | object | Optional extra HTTP headers for gated instances. |

#### Example — newest videos from Framatube

```json
{ "instanceUrl": "https://framatube.org", "mode": "videos", "maxRecords": 50 }
```

#### Example — search a large instance

```json
{ "instanceUrl": "https://makertube.net", "search": "blender", "maxRecords": 100 }
```

#### Example — inspect an instance cheaply

```json
{ "instanceUrl": "https://tube.tchncs.de", "listOnly": true }
```

### Output

One flat, fully-nullable row per record. Video rows include: `id`, `uuid`, `shortUUID`, `name`,
`description`, **`url`** (canonical watch URL), `duration`, `views`, `likes`, `dislikes`, `nsfw`,
`isLive`, `publishedAt`, `originallyPublishedAt`, promoted `category` / `licence` / `language` /
`privacy` labels, `channelName` / `channelDisplayName` / `channelUrl`, `accountName` /
`accountUrl`, **`host`** (the real federating instance), `thumbnailPath` / `previewPath` /
`embedPath`, plus a lossless `_raw` object holding the complete original record.

```json
{
  "_type": "video",
  "_source": "https://framatube.org",
  "_mode": "videos",
  "name": "Wie fährst du dein Projekt zuverlässig an die Wand?",
  "url": "https://tube.tchncs.de/videos/watch/007c7bd1-5860-40f4-9850-e8c57a0867c1",
  "duration": 3799,
  "views": 4,
  "likes": 1,
  "category": "Science & Technology",
  "language": "German",
  "channelDisplayName": "Software Architektur im Stream",
  "host": "tube.tchncs.de",
  "publishedAt": "2026-07-06T09:00:32.097Z"
}
```

### Pricing

Pay-per-event: one **Video** event per emitted row (video, channel, or instance record). You only
pay for records you receive.

### Notes

- An empty instance or a no-match search returns 0 records and a clean exit.
- A URL that is not a PeerTube instance (an HTML page, a 404, JSON without the expected shape)
  fails fast with a clear message — nothing is fabricated.
- Find instances via the [PeerTube instances directory](https://instances.joinpeertube.org/).

# Actor input Schema

## `instanceUrl` (type: `string`):

Root URL of ANY PeerTube instance. PeerTube is the ActivityPub-federated, self-hosted YouTube alternative; every instance exposes the identical public /api/v1 REST API with no auth for public content, so one actor works against all of them: https://framatube.org, https://makertube.net, https://tube.tchncs.de, or any of the thousands of self-hosted instances. The /api/v1 path is appended for you (paste either the root or a /api/v1 URL). The run MODE is chosen from the 'mode' / 'listOnly' / 'search' fields below.

## `mode` (type: `string`):

What to harvest. 'videos' (default) lists videos via /api/v1/videos (or /api/v1/search/videos when a search term is set). 'channels' lists the instance's video channels via /api/v1/video-channels. 'discovery' returns ONE row describing the instance (name, version, signup policy, short description, total video count) so you can inspect an instance cheaply before a full run.

## `search` (type: `string`):

VIDEOS MODE. A free-text search term — switches the run to GET /api/v1/search/videos?search=... and returns one row per matching video across the instance's federated index. Leave empty to list the instance's full video timeline (newest first). A term that matches nothing yields 0 records and a clean exit.

## `sort` (type: `string`):

VIDEOS MODE. Sort order for the video listing. Defaults to -publishedAt (newest first). PeerTube commonly accepts: -publishedAt, publishedAt, -views, views, -likes, -trending, -hot, name, -duration. Left as free text so any instance's accepted value passes through.

## `includeNsfw` (type: `boolean`):

VIDEOS MODE. When false (default) NSFW videos are excluded (nsfw=false). Set true to include them (nsfw=both).

## `listOnly` (type: `boolean`):

Shortcut for discovery mode: when true, ignore 'mode' and emit ONLY the single instance-info row (name, version, signup, total video count). A cheap way to probe an instance before harvesting it.

## `maxRecords` (type: `integer`):

A GLOBAL cap on the number of rows to emit. The rendered default is 25, so a first run emits at most 25 rows. Each emitted video, channel, or discovery row is one billable `video` event under the current pricing contract. Pagination stops as soon as the cap is reached, so the default is a cheap, deterministic sample. Raise the value explicitly to fetch more matching rows.

## `bearer` (type: `string`):

Optional access token for a private / registration-required / rate-limited instance (sent as Authorization: Bearer \*\*\*). Not required for public content. Never logged.

## `extraHeaders` (type: `object`):

Optional extra HTTP headers as a JSON object, e.g. {"x-api-key": "..."} for gated instances. Not required for public content. Header values are never logged.

## Actor input object example

```json
{
  "instanceUrl": "https://framatube.org",
  "mode": "videos",
  "sort": "-publishedAt",
  "includeNsfw": false,
  "listOnly": false,
  "maxRecords": 25
}
```

# Actor output Schema

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

No description

# 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 = {
    "instanceUrl": "https://framatube.org",
    "maxRecords": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("datamule/peertube-instance-harvester").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 = {
    "instanceUrl": "https://framatube.org",
    "maxRecords": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("datamule/peertube-instance-harvester").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "instanceUrl": "https://framatube.org",
  "maxRecords": 25
}' |
apify call datamule/peertube-instance-harvester --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=datamule/peertube-instance-harvester",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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