VCP 1 Documentation›API Reference
Collections
The two required list endpoints: your collections, and the ordered video summaries inside each one.
A collection is an ordered set of videos — a playlist, a series, a shelf, a folder. Collections are the unit of import: users pick which of your collections to bring into their library. Both routes here are required, paginated, and read-only.
List collections
GET/collections
Returns your collections. Supports limit and cursor per thepagination convention.
curl "https://api.example.com/vcp/v1/collections?limit=50" \
-H "VCP-Service-Token: vcps_a81f42..."import requests
API = "https://api.example.com/vcp/v1"
HEADERS = {"VCP-Service-Token": "vcps_a81f42..."}
resp = requests.get(f"{API}/collections", params={"limit": 50}, headers=HEADERS)
resp.raise_for_status()
data = resp.json()
for collection in data["collections"]:
print(f'{collection["id"]} {collection["title"]} ({collection["item_count"]} items)')
print("next page cursor:", data.get("next_cursor"))require "rest-client"
require "json"
API = "https://api.example.com/vcp/v1"
HEADERS = { "VCP-Service-Token" => "vcps_a81f42..." }
data = JSON.parse(
RestClient.get("#{API}/collections", HEADERS.merge(params: { limit: 50 })).body
)
data["collections"].each do |collection|
puts "#{collection["id"]} #{collection["title"]} (#{collection["item_count"]} items)"
end
puts "next page cursor: #{data["next_cursor"].inspect}"const API = 'https://api.example.com/vcp/v1'
const HEADERS = { 'VCP-Service-Token': 'vcps_a81f42...' }
const resp = await fetch(API + '/collections?limit=50', { headers: HEADERS })
if (!resp.ok) throw new Error('VCP request failed: ' + resp.status)
const data = await resp.json()
for (const collection of data.collections) {
console.log(collection.id, collection.title, '(' + collection.item_count + ' items)')
}
console.log('next page cursor:', data.next_cursor){
"collections": [
{
"id": "col_summer24",
"title": "Summer 2024",
"description": "Everything from the summer season.",
"cover_url": "https://cdn.example.com/covers/summer24.jpg",
"item_count": 132,
"updated_at": "2026-07-01T12:00:00Z"
}
],
"next_cursor": "eyJvIjoxMDB9"
}| Field | Description |
|---|---|
id | Opaque, stable-forever id (data rules). |
title | Display name. |
description | Short description, rendered as plain text. |
cover_url | Collection artwork (HTTPS). |
item_count | Number of videos in the collection. |
updated_at | Must change whenever the collection or its items change — it's the re-sync signal for clients without delta sync. |
List a collection's videos
GET/collections/{id}/items
Returns video summaries in your intended order. Summaries carry enough for lists and grids; the full metadata and playable sources come fromthe video detail endpoint at playback time.
curl "https://api.example.com/vcp/v1/collections/col_summer24/items?limit=100" \
-H "VCP-Service-Token: vcps_a81f42..."import requests
API = "https://api.example.com/vcp/v1"
HEADERS = {"VCP-Service-Token": "vcps_a81f42..."}
items, cursor = [], None
while True:
params = {"limit": 100}
if cursor:
params["cursor"] = cursor
resp = requests.get(f"{API}/collections/col_summer24/items",
params=params, headers=HEADERS)
resp.raise_for_status()
data = resp.json()
items += data["items"]
cursor = data.get("next_cursor")
if not cursor:
break
for item in items:
print(item.get("episode_number"), item["title"], f'{item["duration"]}s')require "rest-client"
require "json"
API = "https://api.example.com/vcp/v1"
HEADERS = { "VCP-Service-Token" => "vcps_a81f42..." }
items = []
cursor = nil
loop do
params = { limit: 100 }
params[:cursor] = cursor if cursor
resp = RestClient.get("#{API}/collections/col_summer24/items",
HEADERS.merge(params: params))
data = JSON.parse(resp.body)
items.concat(data["items"])
cursor = data["next_cursor"]
break if cursor.nil?
end
items.each { |item| puts "#{item["episode_number"]} #{item["title"]} #{item["duration"]}s" }const API = 'https://api.example.com/vcp/v1'
const HEADERS = { 'VCP-Service-Token': 'vcps_a81f42...' }
const items = []
let cursor = null
do {
const url = new URL(API + '/collections/col_summer24/items')
url.searchParams.set('limit', 100)
if (cursor) url.searchParams.set('cursor', cursor)
const resp = await fetch(url, { headers: HEADERS })
if (!resp.ok) throw new Error('VCP request failed: ' + resp.status)
const data = await resp.json()
items.push(...data.items)
cursor = data.next_cursor
} while (cursor)
for (const item of items) console.log(item.episode_number, item.title, item.duration + 's'){
"items": [
{
"id": "vid_88213",
"title": "Episode 1 - Arrival",
"duration": 1440,
"thumbnail_url": "https://cdn.example.com/thumbs/88213.jpg",
"poster_url": "https://cdn.example.com/posters/88213.jpg",
"cover_url": "https://cdn.example.com/covers/88213.jpg",
"episode_number": 1,
"updated_at": "2026-06-28T08:11:00Z"
}
],
"next_cursor": null
}Of the three artwork fields only thumbnail_url is required — seeartwork for shapes and fallback order. Fields likeepisode_number are optional; unknown extra fields are ignored by consumers, so you can add your own with an x_ prefix.