Skip to content

PeerTube API

The TrueScrape PeerTube API exposes 7 public endpoints covering Channel profile, Channel videos, Search videos, Video details and Video comments. Every call is a GET against public, logged-out pages and returns the same unified schema as every other platform here. Calls cost 1 credit each. A cache hit is free, and a failed or empty response is never charged.

Endpoints

EndpointReturnsCreditsCacheableBatchable
/v1/peertube/channelChannel profile1yesyes
/v1/peertube/channel/videosChannel videos1yesyes
/v1/peertube/searchSearch videos1yesyes
/v1/peertube/videoVideo details1yesyes
/v1/peertube/video/commentsVideo comments1yesyes
/v1/peertube/video/comments/repliesComment replies1yesyes
/v1/peertube/video/transcriptVideo transcript1yesyes

Reference

GET/v1/peertube/channel1 creditcacheablebatchable

Channel profile

Public profile of a PeerTube channel, read from the instance that hosts it: followers, following, video count, avatar, banner and creation date.

ParameterTypeRequiredDescription
handlestringyesChannel as name@instance (blender_channel@video.blender.org) or a channel URL on its instance

Also accepts cache_max_age (a hit costs 0 credits) and include_raw (returns the untouched upstream payload under raw).

Returns one Creator.

Fields
FieldTypePresentDescription
platformone of 40 stringsalways
idstringalwaysThe platform's own id for the account, such as a channel id or a numeric user id, always as a string.
handlestring | nullalwaysThe account handle without the leading @. Null when the platform has no handles.
displayNamestring | nullalways
biostring | nullalways
avatarUrlstring | nullalways
bannerUrlstring | nullalways
urlstring | nullalways
verifiedboolean | nullalwaysWhether the platform shows a verification badge on the account. Null where the platform has no public badge or does not expose it.
followerCountnumber | nullalwaysFollowers, subscribers, or the platform's nearest equivalent. Null when the account hides the count or the platform does not expose it.
followingCountnumber | nullalways
postCountnumber | nullalwaysPosts, videos, tracks, repositories, or the platform's nearest equivalent that the account has published.
viewCountnumber | nullalwaysLifetime views across the account, where the platform exposes a total.
likeCountnumber | nullmay be absentTotal likes received across all of the creator's posts. Distinct from `viewCount`. Absent when the platform does not expose it.
isPrivateboolean | nullalways
isBusinessboolean | nullalwaysWhether the platform classifies the account as a business or organisation rather than a person or creator. Null where the platform draws no such line or does not expose it. On Facebook every Page counts, including Pages for public figures.
categorystring | nullalwaysA label the platform attaches to the account, such as a business category, a genre or an account type. What it holds varies by platform.
locationstring | nullalwaysLocation as the platform shows it: a city, a country, a region code or an address, depending on the platform.
externalLinksstring[]alwaysLinks the account lists on its profile, such as a website or other social accounts.
createdAtstring | nullalwaysWhen the account was created. Usually an ISO 8601 timestamp; some platforms expose only the date as shown on the profile.
fetchedAtstringalwaysWhen this record was fetched from the platform, as an ISO 8601 timestamp. A cached response keeps the time of the original fetch.
curl
curl "$API/v1/peertube/channel?handle=%40mkbhd&cache_max_age=7d" \
  -H "x-api-key: $KEY"
TypeScript
const query = new URLSearchParams({
  handle: '@mkbhd',
  cache_max_age: '7d',
});

const response = await fetch(`${API}/v1/peertube/channel?${query}`, {
  headers: { 'x-api-key': KEY },
});

const body = await response.json();
if (!body.success) throw new Error(body.error.code);

// 1 credit, 0 on a cache hit
console.log(body.data, body.meta.creditsCharged);
Python
Python
import os, httpx

r = httpx.get(
    f"{os.environ['API']}/v1/peertube/channel",
    params={
        "handle": "@mkbhd",
        "cache_max_age": "7d",
    },
    headers={"x-api-key": os.environ["KEY"]},
    timeout=30,
)

body = r.json()
if not body["success"]:
    raise RuntimeError(body["error"]["code"])

print(body["data"], body["meta"]["creditsCharged"])
Response codes
200Success
400invalid_request. Not charged
401missing_api_key / invalid_api_key / revoked_api_key. Never a billing error
402insufficient_credits. The key is valid, the balance is not
429daily_cap_exceeded, upstream_rate_limited, or rate_limited (hourly calls to servers you named outside our known list). Not charged
501not_configured. This deployment is not set up to serve this endpoint. Not charged.
502upstream_blocked / upstream_schema_drift. Not charged
504upstream_timeout. Not charged
Try it here
GET/v1/peertube/channel
Open the full playground
GET/v1/peertube/channel/videos1 creditcacheablebatchable

Channel videos

A PeerTube channel's public videos, newest first, 50 per page. Tags are not included in list items; peertube.video returns them. Pass the cursor from the previous response to page.

ParameterTypeRequiredDescription
handlestringyesChannel as name@instance (blender_channel@video.blender.org) or a channel URL on its instance
cursorstringnopagination.cursor from the previous response

Also accepts cache_max_age (a hit costs 0 credits) and include_raw (returns the untouched upstream payload under raw).

Returns a list of Post in data.items.

Fields
FieldTypePresentDescription
platformone of 40 stringsalways
idstringalways
typevideo | short | image | carousel | text | live | story | reel | audio | album | episode | unknownalwaysWhat kind of post this is. `short` and `reel` are short-form video formats, `live` is a live stream or a recording of one, `carousel` holds several images or videos, `audio` is a music track, `album` is an album, `episode` is a podcast episode, and `unknown` covers anything else.
urlstring | nullalways
titlestring | nullalways
textstring | nullalwaysThe post's text: a caption, a description or the message body.
authorIdstring | nullalways
authorHandlestring | nullalways
authorNamestring | nullalways
thumbnailUrlstring | nullalways
mediaUrlsstring[]always
durationSecondsnumber | nullalways
viewCountnumber | nullalwaysViews. Null means the platform did not expose a count, which is not the same as zero.
likeCountnumber | nullalwaysLikes, or the platform's nearest equivalent. Null means not exposed, not zero.
commentCountnumber | nullalwaysComments. Null means not exposed, not zero.
shareCountnumber | nullalwaysShares, reposts, or the platform's nearest equivalent. Null means not exposed, not zero.
hashtagsstring[]alwaysHashtags or the platform's own topic tags, without the leading #.
mentionsstring[]alwaysHandles mentioned in the post text, without the leading @.
taggedUsersstring[]may be absentUsernames tagged in the media itself, as distinct from `mentions`, which come from the caption text. Absent when the platform does not report tags; an empty array means it reports none.
isSponsoredboolean | nullalwaysWhether the platform labels the post as an ad, promoted content or a paid partnership.
publishedAtstring | nullalwaysWhen the post was published. Usually an ISO 8601 timestamp; some platforms expose only a date or relative text such as "3 days ago".
fetchedAtstringalwaysWhen this record was fetched from the platform, as an ISO 8601 timestamp. A cached response keeps the time of the original fetch.
curl
curl "$API/v1/peertube/channel/videos?handle=%40mkbhd&cache_max_age=7d" \
  -H "x-api-key: $KEY"
TypeScript
const query = new URLSearchParams({
  handle: '@mkbhd',
  cache_max_age: '7d',
});

const response = await fetch(`${API}/v1/peertube/channel/videos?${query}`, {
  headers: { 'x-api-key': KEY },
});

const body = await response.json();
if (!body.success) throw new Error(body.error.code);

// 1 credit, 0 on a cache hit
console.log(body.data, body.meta.creditsCharged);
Python
Python
import os, httpx

r = httpx.get(
    f"{os.environ['API']}/v1/peertube/channel/videos",
    params={
        "handle": "@mkbhd",
        "cache_max_age": "7d",
    },
    headers={"x-api-key": os.environ["KEY"]},
    timeout=30,
)

body = r.json()
if not body["success"]:
    raise RuntimeError(body["error"]["code"])

print(body["data"], body["meta"]["creditsCharged"])
Response codes
200Success
400invalid_request. Not charged
401missing_api_key / invalid_api_key / revoked_api_key. Never a billing error
402insufficient_credits. The key is valid, the balance is not
429daily_cap_exceeded, upstream_rate_limited, or rate_limited (hourly calls to servers you named outside our known list). Not charged
501not_configured. This deployment is not set up to serve this endpoint. Not charged.
502upstream_blocked / upstream_schema_drift. Not charged
504upstream_timeout. Not charged
Try it here
GET/v1/peertube/channel/videos
Open the full playground
GET/v1/peertube/video1 creditcacheablebatchable

Video details

Public metadata for a PeerTube video: views, likes, comment count, duration, tags, publish date and channel.

ParameterTypeRequiredDescription
urlstringyesVideo URL (https://video.blender.org/w/9UhsEGd8vM4PTtg6bphpEM) or <video id>@<instance>

Also accepts cache_max_age (a hit costs 0 credits) and include_raw (returns the untouched upstream payload under raw).

Returns one Post.

Fields
FieldTypePresentDescription
platformone of 40 stringsalways
idstringalways
typevideo | short | image | carousel | text | live | story | reel | audio | album | episode | unknownalwaysWhat kind of post this is. `short` and `reel` are short-form video formats, `live` is a live stream or a recording of one, `carousel` holds several images or videos, `audio` is a music track, `album` is an album, `episode` is a podcast episode, and `unknown` covers anything else.
urlstring | nullalways
titlestring | nullalways
textstring | nullalwaysThe post's text: a caption, a description or the message body.
authorIdstring | nullalways
authorHandlestring | nullalways
authorNamestring | nullalways
thumbnailUrlstring | nullalways
mediaUrlsstring[]always
durationSecondsnumber | nullalways
viewCountnumber | nullalwaysViews. Null means the platform did not expose a count, which is not the same as zero.
likeCountnumber | nullalwaysLikes, or the platform's nearest equivalent. Null means not exposed, not zero.
commentCountnumber | nullalwaysComments. Null means not exposed, not zero.
shareCountnumber | nullalwaysShares, reposts, or the platform's nearest equivalent. Null means not exposed, not zero.
hashtagsstring[]alwaysHashtags or the platform's own topic tags, without the leading #.
mentionsstring[]alwaysHandles mentioned in the post text, without the leading @.
taggedUsersstring[]may be absentUsernames tagged in the media itself, as distinct from `mentions`, which come from the caption text. Absent when the platform does not report tags; an empty array means it reports none.
isSponsoredboolean | nullalwaysWhether the platform labels the post as an ad, promoted content or a paid partnership.
publishedAtstring | nullalwaysWhen the post was published. Usually an ISO 8601 timestamp; some platforms expose only a date or relative text such as "3 days ago".
fetchedAtstringalwaysWhen this record was fetched from the platform, as an ISO 8601 timestamp. A cached response keeps the time of the original fetch.
curl
curl "$API/v1/peertube/video?url=8XkPqR2nLvE&cache_max_age=7d" \
  -H "x-api-key: $KEY"
TypeScript
const query = new URLSearchParams({
  url: '8XkPqR2nLvE',
  cache_max_age: '7d',
});

const response = await fetch(`${API}/v1/peertube/video?${query}`, {
  headers: { 'x-api-key': KEY },
});

const body = await response.json();
if (!body.success) throw new Error(body.error.code);

// 1 credit, 0 on a cache hit
console.log(body.data, body.meta.creditsCharged);
Python
Python
import os, httpx

r = httpx.get(
    f"{os.environ['API']}/v1/peertube/video",
    params={
        "url": "8XkPqR2nLvE",
        "cache_max_age": "7d",
    },
    headers={"x-api-key": os.environ["KEY"]},
    timeout=30,
)

body = r.json()
if not body["success"]:
    raise RuntimeError(body["error"]["code"])

print(body["data"], body["meta"]["creditsCharged"])
Response codes
200Success
400invalid_request. Not charged
401missing_api_key / invalid_api_key / revoked_api_key. Never a billing error
402insufficient_credits. The key is valid, the balance is not
429daily_cap_exceeded, upstream_rate_limited, or rate_limited (hourly calls to servers you named outside our known list). Not charged
501not_configured. This deployment is not set up to serve this endpoint. Not charged.
502upstream_blocked / upstream_schema_drift. Not charged
504upstream_timeout. Not charged
Try it here
GET/v1/peertube/video
Open the full playground
GET/v1/peertube/video/comments1 creditcacheablebatchable

Video comments

Top-level comments on a PeerTube video, newest first, 100 per page, including those posted from other fediverse servers. replyCount gives each thread's replies; peertube.commentReplies returns them.

ParameterTypeRequiredDescription
urlstringyesVideo URL (https://video.blender.org/w/9UhsEGd8vM4PTtg6bphpEM) or <video id>@<instance>
cursorstringnopagination.cursor from the previous response

Also accepts cache_max_age (a hit costs 0 credits) and include_raw (returns the untouched upstream payload under raw).

Returns a list of Comment in data.items.

Fields
FieldTypePresentDescription
platformone of 40 stringsalways
idstringalways
postIdstring | nullalways
parentIdstring | nullalwaysId of the comment this one replies to. Null on a top-level comment.
textstringalways
authorIdstring | nullalways
authorHandlestring | nullalways
authorNamestring | nullalways
authorAvatarUrlstring | nullalways
likeCountnumber | nullalways
replyCountnumber | nullalways
isPinnedboolean | nullalways
isAuthorReplyboolean | nullalwaysWhether the comment was written by the author of the post.
publishedAtstring | nullalwaysWhen the comment was posted. Usually an ISO 8601 timestamp; some platforms expose only relative text such as "2 days ago".
curl
curl "$API/v1/peertube/video/comments?url=8XkPqR2nLvE&cache_max_age=7d" \
  -H "x-api-key: $KEY"
TypeScript
const query = new URLSearchParams({
  url: '8XkPqR2nLvE',
  cache_max_age: '7d',
});

const response = await fetch(`${API}/v1/peertube/video/comments?${query}`, {
  headers: { 'x-api-key': KEY },
});

const body = await response.json();
if (!body.success) throw new Error(body.error.code);

// 1 credit, 0 on a cache hit
console.log(body.data, body.meta.creditsCharged);
Python
Python
import os, httpx

r = httpx.get(
    f"{os.environ['API']}/v1/peertube/video/comments",
    params={
        "url": "8XkPqR2nLvE",
        "cache_max_age": "7d",
    },
    headers={"x-api-key": os.environ["KEY"]},
    timeout=30,
)

body = r.json()
if not body["success"]:
    raise RuntimeError(body["error"]["code"])

print(body["data"], body["meta"]["creditsCharged"])
Response codes
200Success
400invalid_request. Not charged
401missing_api_key / invalid_api_key / revoked_api_key. Never a billing error
402insufficient_credits. The key is valid, the balance is not
429daily_cap_exceeded, upstream_rate_limited, or rate_limited (hourly calls to servers you named outside our known list). Not charged
501not_configured. This deployment is not set up to serve this endpoint. Not charged.
502upstream_blocked / upstream_schema_drift. Not charged
504upstream_timeout. Not charged
Try it here
GET/v1/peertube/video/comments
Open the full playground
GET/v1/peertube/video/comments/replies1 creditcacheablebatchable

Comment replies

Every reply under one top-level comment on a PeerTube video, nested replies included, each with parentId naming the comment it answers.

ParameterTypeRequiredDescription
urlstringyesVideo URL (https://video.blender.org/w/9UhsEGd8vM4PTtg6bphpEM) or <video id>@<instance>
commentIdstringyesid of a top-level comment from peertube.comments

Also accepts cache_max_age (a hit costs 0 credits) and include_raw (returns the untouched upstream payload under raw).

Returns a list of Comment in data.items.

Fields
FieldTypePresentDescription
platformone of 40 stringsalways
idstringalways
postIdstring | nullalways
parentIdstring | nullalwaysId of the comment this one replies to. Null on a top-level comment.
textstringalways
authorIdstring | nullalways
authorHandlestring | nullalways
authorNamestring | nullalways
authorAvatarUrlstring | nullalways
likeCountnumber | nullalways
replyCountnumber | nullalways
isPinnedboolean | nullalways
isAuthorReplyboolean | nullalwaysWhether the comment was written by the author of the post.
publishedAtstring | nullalwaysWhen the comment was posted. Usually an ISO 8601 timestamp; some platforms expose only relative text such as "2 days ago".
curl
curl "$API/v1/peertube/video/comments/replies?url=8XkPqR2nLvE&commentId=%3CcommentId%3E&cache_max_age=7d" \
  -H "x-api-key: $KEY"
TypeScript
const query = new URLSearchParams({
  url: '8XkPqR2nLvE',
  commentId: '<commentId>',
  cache_max_age: '7d',
});

const response = await fetch(`${API}/v1/peertube/video/comments/replies?${query}`, {
  headers: { 'x-api-key': KEY },
});

const body = await response.json();
if (!body.success) throw new Error(body.error.code);

// 1 credit, 0 on a cache hit
console.log(body.data, body.meta.creditsCharged);
Python
Python
import os, httpx

r = httpx.get(
    f"{os.environ['API']}/v1/peertube/video/comments/replies",
    params={
        "url": "8XkPqR2nLvE",
        "commentId": "<commentId>",
        "cache_max_age": "7d",
    },
    headers={"x-api-key": os.environ["KEY"]},
    timeout=30,
)

body = r.json()
if not body["success"]:
    raise RuntimeError(body["error"]["code"])

print(body["data"], body["meta"]["creditsCharged"])
Response codes
200Success
400invalid_request. Not charged
401missing_api_key / invalid_api_key / revoked_api_key. Never a billing error
402insufficient_credits. The key is valid, the balance is not
429daily_cap_exceeded, upstream_rate_limited, or rate_limited (hourly calls to servers you named outside our known list). Not charged
501not_configured. This deployment is not set up to serve this endpoint. Not charged.
502upstream_blocked / upstream_schema_drift. Not charged
504upstream_timeout. Not charged
Try it here
GET/v1/peertube/video/comments/replies
Open the full playground
GET/v1/peertube/video/transcript1 creditcacheablebatchable

Video transcript

A PeerTube video's captions as one text block plus timed cues. Returns the requested language when the video has it, otherwise the first track. isAutoGenerated says whether the instance transcribed it automatically.

ParameterTypeRequiredDescription
urlstringyesVideo URL (https://video.blender.org/w/9UhsEGd8vM4PTtg6bphpEM) or <video id>@<instance>
languagestringnoLanguage code, e.g. "en"

Also accepts cache_max_age (a hit costs 0 credits) and include_raw (returns the untouched upstream payload under raw).

Returns one Transcript.

Fields
FieldTypePresentDescription
platformone of 40 stringsalways
postIdstringalways
urlstring | nullalways
languagestring | nullalwaysThe transcript's language as the platform labels it, usually a language code such as `en`.
isAutoGeneratedboolean | nullalwaysWhether the platform generated the captions automatically, by speech recognition or machine translation, rather than a person writing them.
textstringalwaysThe full transcript as a single block of text.
cuesobject[]alwaysTimed segments of the transcript.
cues[].startnumberalwaysSeconds from the start of the media.
cues[].endnumber | nullalwaysSeconds from the start of the media. Null when the platform gives no end time.
cues[].textstringalways
durationSecondsnumber | nullalways
sourcecaptions | asr | nullalwaysWhere the transcript came from. `captions`: the platform's own caption track, whether a person wrote it or the platform generated it (see `isAutoGenerated`). `asr`: produced by speech recognition on the audio. Null when this cannot be told.
fetchedAtstringalwaysWhen this record was fetched from the platform, as an ISO 8601 timestamp. A cached response keeps the time of the original fetch.
curl
curl "$API/v1/peertube/video/transcript?url=8XkPqR2nLvE&cache_max_age=7d" \
  -H "x-api-key: $KEY"
TypeScript
const query = new URLSearchParams({
  url: '8XkPqR2nLvE',
  cache_max_age: '7d',
});

const response = await fetch(`${API}/v1/peertube/video/transcript?${query}`, {
  headers: { 'x-api-key': KEY },
});

const body = await response.json();
if (!body.success) throw new Error(body.error.code);

// 1 credit, 0 on a cache hit
console.log(body.data, body.meta.creditsCharged);
Python
Python
import os, httpx

r = httpx.get(
    f"{os.environ['API']}/v1/peertube/video/transcript",
    params={
        "url": "8XkPqR2nLvE",
        "cache_max_age": "7d",
    },
    headers={"x-api-key": os.environ["KEY"]},
    timeout=30,
)

body = r.json()
if not body["success"]:
    raise RuntimeError(body["error"]["code"])

print(body["data"], body["meta"]["creditsCharged"])
Response codes
200Success
400invalid_request. Not charged
401missing_api_key / invalid_api_key / revoked_api_key. Never a billing error
402insufficient_credits. The key is valid, the balance is not
429daily_cap_exceeded, upstream_rate_limited, or rate_limited (hourly calls to servers you named outside our known list). Not charged
501not_configured. This deployment is not set up to serve this endpoint. Not charged.
502upstream_blocked / upstream_schema_drift. Not charged
504upstream_timeout. Not charged
Try it here
GET/v1/peertube/video/transcript
Open the full playground

Questions

How much does the PeerTube API cost?
Calls cost 1 credit each. Every endpoint is cacheable, and a cache hit costs nothing. A failed request and an empty result are both free, on every endpoint.
Does the PeerTube API need a login or cookies?
No. Every endpoint reads public, logged-out pages only — no account, no cookies, no session. An API key identifies your own TrueScrape account and nothing else.
Can I fetch many PeerTube targets in one request?
Yes. 7 of 7 are batchable: one POST to /v1/jobs/batch takes many targets and returns a job id to poll.
Can I watch PeerTube endpoints for changes?
Yes, 6 of 7. A subscription polls on your schedule and fires your webhook only when the content changed; the rest carry x-subscribable: false in the spec.