VCP 1 Documentation›API Reference
Playlists
User-owned playlists stored on your service — distinct from your own collections — with create, edit, and a conflict-safe reorder.
Advertise the playlists keyword in your manifest'scapabilities to mount all eight routes below. Playlists are user-owned state: every route requires an identity, writes require the playlists.write scope, and the two reads require onlylibrary.read. Everything here followsthe write model — ownership checks on every call, idempotent verbs, optimistic concurrency.
The playlist object
{
"id": "pl_o2214",
"title": "Rewatch queue",
"description": "",
"item_count": 3,
"cover_url": "https://cdn.example.com/thumbs/88213.jpg",
"version": "v41",
"updated_at": "2026-07-16T09:30:00Z"
}version is an opaque string (also served as the ETag) that changes on every mutation, including item changes. You may cap playlist size via limits.max_playlist_items in your manifest (default 1000).
Routes
| Route | Semantics |
|---|---|
GET /playlists | The requesting user's playlists. Paginated. |
POST /playlists | Create: { "title", "description" } + Idempotency-Key → 201 playlist object. |
PATCH /playlists/{id} | Update metadata (any subset of title, description), If-Match recommended → 200 updated object. |
DELETE /playlists/{id} | Delete → 204, idempotent. |
GET /playlists/{id}/items | Items in order. Paginated. |
POST /playlists/{id}/items | Append { "video_id" }, optional "position" (0-based insert) → 201 item. |
DELETE /playlists/{id}/items/{item_id} | Remove one item → 204, idempotent. |
PUT /playlists/{id}/order | Reorder — see below. |
Playlist items
Items get their own item_id because the same video can appear twice in one playlist:
{
"items": [
{ "item_id": "pli_1", "video_id": "vid_88213", "added_at": "2026-07-10T11:00:00Z" },
{ "item_id": "pli_2", "video_id": "vid_11482", "added_at": "2026-07-11T18:20:00Z" }
],
"next_cursor": null
}Working with playlists
# Create (Idempotency-Key makes retries safe)
curl -X POST "https://api.example.com/vcp/v1/playlists" \
-H "VCP-Service-Token: vcps_a81f42..." \
-H "Authorization: Bearer vcpa_71be02..." \
-H "Idempotency-Key: 9c4a7ee2-51b0-4de1-8a3f-2f4c01a4b9d7" \
-H "Content-Type: application/json" \
-d '{ "title": "Rewatch queue", "description": "" }'
# Append a video
curl -X POST "https://api.example.com/vcp/v1/playlists/pl_o2214/items" \
-H "VCP-Service-Token: vcps_a81f42..." \
-H "Authorization: Bearer vcpa_71be02..." \
-H "Idempotency-Key: 3f7d9a10-6c2e-4b5f-9e81-b04d7c22e6aa" \
-H "Content-Type: application/json" \
-d '{ "video_id": "vid_88213" }'
# Reorder (full order replacement, guarded by If-Match)
curl -X PUT "https://api.example.com/vcp/v1/playlists/pl_o2214/order" \
-H "VCP-Service-Token: vcps_a81f42..." \
-H "Authorization: Bearer vcpa_71be02..." \
-H "If-Match: v41" \
-H "Content-Type: application/json" \
-d '{ "item_ids": ["pli_2", "pli_1"] }'import uuid
import requests
API = "https://api.example.com/vcp/v1"
HEADERS = {
"VCP-Service-Token": "vcps_a81f42...",
"Authorization": "Bearer vcpa_71be02...",
}
# Create - safe to retry with the same Idempotency-Key
playlist = requests.post(f"{API}/playlists",
headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
json={"title": "Rewatch queue", "description": ""}).json()
# Append a video
requests.post(f"{API}/playlists/{playlist['id']}/items",
headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
json={"video_id": "vid_88213"}).raise_for_status()
# Reorder with optimistic concurrency
resp = requests.put(f"{API}/playlists/{playlist['id']}/order",
headers={**HEADERS, "If-Match": playlist["version"]},
json={"item_ids": ["pli_2", "pli_1"]})
if resp.status_code == 412:
# Someone else changed the playlist: refetch items + version,
# reapply the user's intended order, and retry once.
passrequire "rest-client"
require "json"
require "securerandom"
API = "https://api.example.com/vcp/v1"
HEADERS = {
"VCP-Service-Token" => "vcps_a81f42...",
"Authorization" => "Bearer vcpa_71be02...",
content_type: :json
}
# Create - safe to retry with the same Idempotency-Key
playlist = JSON.parse(RestClient.post("#{API}/playlists",
{ title: "Rewatch queue", description: "" }.to_json,
HEADERS.merge("Idempotency-Key" => SecureRandom.uuid)).body)
# Append a video
RestClient.post("#{API}/playlists/#{playlist["id"]}/items",
{ video_id: "vid_88213" }.to_json,
HEADERS.merge("Idempotency-Key" => SecureRandom.uuid))
# Reorder with optimistic concurrency
begin
RestClient.put("#{API}/playlists/#{playlist["id"]}/order",
{ item_ids: ["pli_2", "pli_1"] }.to_json,
HEADERS.merge("If-Match" => playlist["version"]))
rescue RestClient::PreconditionFailed
# Someone else changed the playlist: refetch items + version,
# reapply the user's intended order, and retry once.
endimport { randomUUID } from 'node:crypto'
const API = 'https://api.example.com/vcp/v1'
const HEADERS = {
'VCP-Service-Token': 'vcps_a81f42...',
'Authorization': 'Bearer vcpa_71be02...',
'Content-Type': 'application/json',
}
// Create - safe to retry with the same Idempotency-Key
const playlist = await (await fetch(API + '/playlists', {
method: 'POST',
headers: { ...HEADERS, 'Idempotency-Key': randomUUID() },
body: JSON.stringify({ title: 'Rewatch queue', description: '' }),
})).json()
// Append a video
await fetch(API + '/playlists/' + playlist.id + '/items', {
method: 'POST',
headers: { ...HEADERS, 'Idempotency-Key': randomUUID() },
body: JSON.stringify({ video_id: 'vid_88213' }),
})
// Reorder with optimistic concurrency
const reorder = await fetch(API + '/playlists/' + playlist.id + '/order', {
method: 'PUT',
headers: { ...HEADERS, 'If-Match': playlist.version },
body: JSON.stringify({ item_ids: ['pli_2', 'pli_1'] }),
})
if (reorder.status === 412) {
// Someone else changed the playlist: refetch items + version,
// reapply the user's intended order, and retry once.
}Reordering
Reordering is a full-order replacement — one idempotent request, no move-operation grammar to get wrong. Send the complete list of current item ids in the new order, guarded by If-Match:
- Success →
200with the updated playlist object (newversion). - The
item_idsarray must contain exactly the playlist's current item ids — a mismatch is409 conflict. - A stale
If-Matchis412 precondition_failed— the client refetches items and reapplies the user's intended order.