VCP 1 Documentation›Essentials
Getting Started
Go from zero to a conforming VCP provider: four routes, one manifest, one DNS record.
The smallest valid VCP provider serves four read-only JSON routes, checks one header, and registers one manifest. If your catalog is public and your media URLs are permanent, that's the whole job — you can layer onauthentication andcapabilities later without breaking anything.
Step 1 — Serve the four required routes
All paths are relative to the api_base you'll declare in your manifest:
| Route | Returns |
|---|---|
GET /collections | Your collections — playlists, series, shelves. |
GET /collections/{id}/items | The video summaries inside one collection, in order. |
GET /videos/{id} | One video's full detail: metadata, playable sources, subtitles. |
GET /videos/{id}/sources | Just the sources and subtitles, for cheap refresh of expiring URLs. |
Every route must validate the VCP-Service-Token header — seethe service token. Nothing on your VCP API is ever public.
Step 2 — Write your manifest
The manifest describes your integration: where your API lives, how users authenticate, and which optional capabilities you support. A minimal public-catalog manifest:
{
"vcp_version": "1",
"publisher_id": "pub_3f92ka",
"name": "Example Media",
"description": "Curated video collections from Example.",
"api_base": "https://api.example.com/vcp/v1",
"auth": { "type": "none" },
"capabilities": []
}"auth": { "type": "none" } means no end-user handshake at all, and an emptycapabilities array means only the four core routes exist. Field-by-field details are in Discovery & the Manifest.
Step 3 — Register as a publisher
Sign up on the VC publisher portal and register yourhttps:// origin, your manifest, and an operational contact — the portal's guided form writes the manifest for you. The portal issues a challenge token; prove you control the domain by publishing it as a DNS TXT record on _vcp.<your-host>:
# Create this TXT record with your DNS host, then confirm it resolves:
dig TXT _vcp.example.com
; ANSWER SECTION:
_vcp.example.com. 300 IN TXT "vcp-verify=chal_7f20c1a9..."Can't edit DNS because your site lives on a shared subdomain (likeyour-app.pages.dev)? Serve the same challenge line as a plain-text file athttps://<your-host>/.well-known/vcp-verify instead — see theHTTP fallback.
Once verified, the platform issues your public publisher_id and yourservice secret (shown once — store it in a secret manager). The full registration flow, including rotation and revocation, is covered inPublisher Registration.
Step 4 — Test your integration
Walk the full required surface exactly the way the gateway will, sending your service token on every request:
# 1. List collections
curl "https://api.example.com/vcp/v1/collections?limit=50" \
-H "VCP-Service-Token: vcps_a81f42..."
# 2. List the videos inside one collection
curl "https://api.example.com/vcp/v1/collections/col_summer24/items" \
-H "VCP-Service-Token: vcps_a81f42..."
# 3. Fetch one video's full detail (metadata + sources + subtitles)
curl "https://api.example.com/vcp/v1/videos/vid_88213" \
-H "VCP-Service-Token: vcps_a81f42..."
# 4. Refresh just the sources (cheap re-sync of expiring signed URLs)
curl "https://api.example.com/vcp/v1/videos/vid_88213/sources" \
-H "VCP-Service-Token: vcps_a81f42..."import requests
API = "https://api.example.com/vcp/v1"
HEADERS = {"VCP-Service-Token": "vcps_a81f42..."}
def get(path, **params):
resp = requests.get(API + path, params=params, headers=HEADERS)
resp.raise_for_status()
return resp.json()
# Walk the whole required surface
collections = get("/collections", limit=50)["collections"]
items = get(f"/collections/{collections[0]['id']}/items")["items"]
video = get(f"/videos/{items[0]['id']}")
sources = get(f"/videos/{items[0]['id']}/sources")
print(video["title"], "-", len(sources["sources"]), "sources")require "rest-client"
require "json"
API = "https://api.example.com/vcp/v1"
HEADERS = { "VCP-Service-Token" => "vcps_a81f42..." }
def get(path, params = {})
JSON.parse(RestClient.get("#{API}#{path}", HEADERS.merge(params: params)).body)
end
# Walk the whole required surface
collections = get("/collections", limit: 50)["collections"]
items = get("/collections/#{collections.first["id"]}/items")["items"]
video = get("/videos/#{items.first["id"]}")
sources = get("/videos/#{items.first["id"]}/sources")
puts "#{video["title"]} - #{sources["sources"].length} sources"const API = 'https://api.example.com/vcp/v1'
const HEADERS = { 'VCP-Service-Token': 'vcps_a81f42...' }
async function get(path) {
const resp = await fetch(API + path, { headers: HEADERS })
if (!resp.ok) throw new Error('VCP request failed: ' + resp.status)
return resp.json()
}
// Walk the whole required surface
const { collections } = await get('/collections?limit=50')
const { items } = await get('/collections/' + collections[0].id + '/items')
const video = await get('/videos/' + items[0].id)
const sources = await get('/videos/' + items[0].id + '/sources')
console.log(video.title, '-', sources.sources.length, 'sources')Before you call it done, verify the failure paths too:
- A request without
VCP-Service-Tokenreturns401with error codeinvalid_service_token. - An unknown video or collection id returns
404with error codenot_found. - Every list route honors
limitand returns a workingnext_cursor— see pagination. - Every object carries an
updated_attimestamp (RFC 3339 UTC).
Where to go next
- Lifecycle — how the app uses your API day to day, from import to re-sync.
- Authentication — add pairing or API keys when your catalog isn't fully public.
- Capabilities — advertise search, delta sync, progress, reactions, or playlists.
- Security Guide — the provider checklist to run before launch.