Documentation contents

VCP 1 Documentation

The Write Model

Six shared rules every write-bearing capability follows, so conflict handling and offline sync are implemented once — not per feature.

Capabilities like reactions andplaylists mutate user-owned state on your service. Rather than each feature inventing its own semantics, every write-bearing capability — standard or vendor — follows these six rules.

1. Identity required

Writes execute against a provider-side user account, so the request must carry a credential you can map to one: aVCP-User-Token or apaired session token. Requests without one get 401 unauthorized. You decide which of your credentials grant which<capability>.write scopes.

2. Ownership enforced

Users can only read and mutate their own objects. IDs of user-owned objects must be authorized against the requesting identity on every call — object ids are not secrets, and guessing one must yield 403 forbidden or404 not_found, never data. This is the classic IDOR trap; don't fall in it.

3. Idempotent verbs

  • State-setting uses PUT — safe to retry blindly.
  • Deletes are idempotent: DELETE of an already-gone object → 204.
  • Creates use POST with an Idempotency-Key header — an opaque client-generated key you should remember for at least 24 hours, replaying the original response on retry instead of creating a duplicate.
# Retrying a create with the SAME Idempotency-Key replays the original
# response instead of creating a duplicate:
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" }'
# Run it twice: both return 201 with the SAME playlist object.

4. Optimistic concurrency

Mutable user objects carry a version (opaque string, also served asETag). Structural mutations — reorder, metadata patch — sendIf-Match: {version}; a stale version gets412 precondition_failed, and the client refetches then reapplies. Simple value-sets (a reaction, watch progress) are last-write-wins and skip versioning entirely.

import requests

API = "https://api.example.com/vcp/v1"
HEADERS = {
    "VCP-Service-Token": "vcps_a81f42...",
    "Authorization": "Bearer vcpa_71be02...",
}

def reorder(playlist_id, intended_order):
    """Apply the user's intended order, refetching once on a version conflict."""
    for _ in range(2):
        playlist = requests.get(f"{API}/playlists/{playlist_id}",
                                headers=HEADERS).json()
        resp = requests.put(f"{API}/playlists/{playlist_id}/order",
            headers={**HEADERS, "If-Match": playlist["version"]},
            json={"item_ids": intended_order})
        if resp.status_code != 412:
            resp.raise_for_status()
            return resp.json()
    raise RuntimeError("playlist kept changing underneath us")

5. Offline queue

Clients may queue writes while offline and replay them in order on reconnect — idempotency is what makes that replay safe. Queued writes that then fail with401/403 are dropped with a user-visible notice, not retried forever.

6. Sync visibility

If you advertise the changes capability, your feed must emit change entries for user-owned objects of every write capability you support, using the same <object>.<created|updated|deleted> pattern (e.g. playlist.updated), scoped to the requesting identity — that's how a user's other devices converge.

Coming soon!

We're putting the finishing touches on VClarity. It'll be on the App Store and Google Play very soon.