Skip to main content
Technology & EngineeringVideo Services288 lines

Vimeo OTT

"Vimeo OTT API: video-on-demand platform, subscription management, customer auth, content libraries, embed players, analytics, webhook events"

Quick Summary24 lines
Vimeo OTT (formerly VHX) is a white-label video monetization platform that lets you build subscription and transactional video-on-demand services under your own brand. Unlike Vimeo's standard platform, OTT is designed for content owners who want to sell access to video libraries via subscriptions (SVOD), rentals, or purchases (TVOD). The API follows REST conventions with OAuth2 authentication. The core data model revolves around Sites (your branded destination), Products (subscription plans or individual purchases), Customers (authenticated viewers), and Videos organized into Collections. Design your integration around customer lifecycle management and entitlement checks — most API calls involve verifying what a customer has purchased and serving them the appropriate content.

## Key Points

- Always verify customer entitlements server-side before serving embed URLs or player tokens.
- Use webhook events for subscription lifecycle tracking instead of polling customer records.
- Organize content into collections with meaningful types (series for episodic, category for browsing).
- Store the Vimeo OTT customer ID alongside your own user records for quick lookups.
- Use the product API to manage multiple subscription tiers or one-time purchase options.
- Implement proper pagination when listing videos, customers, or collections — responses are paginated by default.
- Do not rely on client-side entitlement checks; always verify access on the server before generating embed tokens.
- Do not hardcode product IDs across your app; fetch and cache them so pricing changes don't require code deployments.
- Do not expose your OTT API key to the client; all API calls should be proxied through your server.
- Do not ignore webhook delivery failures; configure retry logic and a dead-letter queue for missed events.
- Do not assume all customers have active subscriptions; always check the `plan` field or product entitlements before granting access.

## Quick Example

```
VIMEO_OTT_API_KEY=your-api-key
VIMEO_OTT_SITE_ID=your-site-id
```
skilldb get video-services-skills/Vimeo OTTFull skill: 288 lines
Paste into your CLAUDE.md or agent config

Vimeo OTT

Core Philosophy

Vimeo OTT (formerly VHX) is a white-label video monetization platform that lets you build subscription and transactional video-on-demand services under your own brand. Unlike Vimeo's standard platform, OTT is designed for content owners who want to sell access to video libraries via subscriptions (SVOD), rentals, or purchases (TVOD). The API follows REST conventions with OAuth2 authentication. The core data model revolves around Sites (your branded destination), Products (subscription plans or individual purchases), Customers (authenticated viewers), and Videos organized into Collections. Design your integration around customer lifecycle management and entitlement checks — most API calls involve verifying what a customer has purchased and serving them the appropriate content.

Setup

Vimeo OTT uses OAuth2 Bearer tokens. Obtain your API key from your OTT admin dashboard:

const OTT_API_KEY = process.env.VIMEO_OTT_API_KEY!;
const OTT_BASE_URL = "https://api.vhx.tv";

async function ottRequest<T>(path: string, options: RequestInit = {}): Promise<T> {
  const res = await fetch(`${OTT_BASE_URL}${path}`, {
    ...options,
    headers: {
      Authorization: `Bearer ${OTT_API_KEY}`,
      "Content-Type": "application/json",
      ...options.headers,
    },
  });
  if (!res.ok) {
    const error = await res.text();
    throw new Error(`Vimeo OTT API error ${res.status}: ${error}`);
  }
  return res.json();
}

Environment variables required:

VIMEO_OTT_API_KEY=your-api-key
VIMEO_OTT_SITE_ID=your-site-id

Key Techniques

Customer Management

interface OttCustomer {
  id: number;
  email: string;
  name: string;
  created_at: string;
  plan: { id: number; name: string } | null;
  _links: Record<string, { href: string }>;
}

// Create a customer account
async function createCustomer(email: string, name: string, password: string): Promise<OttCustomer> {
  return ottRequest<OttCustomer>("/customers", {
    method: "POST",
    body: JSON.stringify({
      email,
      name,
      password,
      product: `https://api.vhx.tv/products/${process.env.VIMEO_OTT_PRODUCT_ID}`,
    }),
  });
}

// Retrieve a customer and their entitlements
async function getCustomer(customerId: number): Promise<OttCustomer> {
  return ottRequest<OttCustomer>(`/customers/${customerId}`);
}

// List all customers with pagination
async function listCustomers(page = 1, perPage = 25) {
  return ottRequest<{ _embedded: { customers: OttCustomer[] }; total: number }>(
    `/customers?page=${page}&per_page=${perPage}`
  );
}

Product and Subscription Management

interface OttProduct {
  id: number;
  name: string;
  price: { cents: number; currency: string };
  product_type: "subscription" | "purchase" | "rental";
  is_active: boolean;
}

async function listProducts(): Promise<OttProduct[]> {
  const result = await ottRequest<{ _embedded: { products: OttProduct[] } }>("/products");
  return result._embedded.products;
}

// Grant a customer access to a product (e.g., after external payment)
async function addProductToCustomer(customerId: number, productId: number) {
  return ottRequest(`/customers/${customerId}/products`, {
    method: "PUT",
    body: JSON.stringify({
      product: `https://api.vhx.tv/products/${productId}`,
    }),
  });
}

// Revoke access
async function removeProductFromCustomer(customerId: number, productId: number) {
  return ottRequest(`/customers/${customerId}/products`, {
    method: "DELETE",
    body: JSON.stringify({
      product: `https://api.vhx.tv/products/${productId}`,
    }),
  });
}

Video and Collection Management

interface OttVideo {
  id: number;
  title: string;
  description: string;
  duration: { seconds: number; formatted: string };
  thumbnail: { small: string; medium: string; large: string };
  status: string;
  created_at: string;
  _links: Record<string, { href: string }>;
}

interface OttCollection {
  id: number;
  name: string;
  description: string;
  items_count: number;
  collection_type: "series" | "movie" | "category" | "playlist";
}

// List videos
async function listVideos(page = 1): Promise<OttVideo[]> {
  const result = await ottRequest<{ _embedded: { videos: OttVideo[] } }>(
    `/videos?page=${page}&per_page=25`
  );
  return result._embedded.videos;
}

// List collections (series, categories)
async function listCollections(): Promise<OttCollection[]> {
  const result = await ottRequest<{ _embedded: { collections: OttCollection[] } }>(
    "/collections"
  );
  return result._embedded.collections;
}

// Get items in a collection
async function getCollectionItems(collectionId: number): Promise<OttVideo[]> {
  const result = await ottRequest<{ _embedded: { items: OttVideo[] } }>(
    `/collections/${collectionId}/items`
  );
  return result._embedded.items;
}

Checking Customer Entitlements

// Verify a customer has access to a specific video before serving it
async function checkVideoAccess(customerId: number, videoId: number): Promise<boolean> {
  try {
    const watchlist = await ottRequest<{ _embedded: { videos: OttVideo[] } }>(
      `/customers/${customerId}/watchlist?video=${videoId}`
    );
    return watchlist._embedded.videos.length > 0;
  } catch {
    return false;
  }
}

// Get customer's authorized videos
async function getCustomerVideos(customerId: number) {
  return ottRequest<{ _embedded: { videos: OttVideo[] } }>(
    `/customers/${customerId}/watchlist`
  );
}

Embedding the Player

// Generate an authenticated embed URL for a customer
function getEmbedUrl(videoId: number): string {
  return `https://embed.vhx.tv/videos/${videoId}`;
}

// React component for embedded player
interface OttPlayerProps {
  videoId: number;
  customerToken: string;
}

function OttPlayer({ videoId, customerToken }: OttPlayerProps) {
  const src = `https://embed.vhx.tv/videos/${videoId}?token=${customerToken}`;

  return (
    <iframe
      src={src}
      width="100%"
      style={{ aspectRatio: "16/9", border: "none" }}
      allowFullScreen
      allow="autoplay; fullscreen"
      referrerPolicy="origin"
    />
  );
}

Webhook Events

interface OttWebhookEvent {
  event: string;
  data: {
    customer_id?: number;
    product_id?: number;
    video_id?: number;
    [key: string]: unknown;
  };
}

async function handleOttWebhook(req: Request): Promise<Response> {
  const event: OttWebhookEvent = await req.json();

  switch (event.event) {
    case "customer.created":
      await onNewCustomer(event.data.customer_id!);
      break;
    case "customer.product.created":
      // Customer subscribed or purchased
      await onSubscription(event.data.customer_id!, event.data.product_id!);
      break;
    case "customer.product.deleted":
      // Subscription canceled or access revoked
      await onCancellation(event.data.customer_id!, event.data.product_id!);
      break;
    case "video.created":
      await onVideoAdded(event.data.video_id!);
      break;
  }

  return new Response("OK", { status: 200 });
}

Analytics

async function getVideoAnalytics(videoId: number, from: string, to: string) {
  return ottRequest(
    `/analytics?video_id=${videoId}&from=${from}&to=${to}&type=traffic`
  );
}

async function getSiteOverview() {
  return ottRequest("/analytics?type=subscribers");
}

Best Practices

  • Always verify customer entitlements server-side before serving embed URLs or player tokens.
  • Use webhook events for subscription lifecycle tracking instead of polling customer records.
  • Organize content into collections with meaningful types (series for episodic, category for browsing).
  • Store the Vimeo OTT customer ID alongside your own user records for quick lookups.
  • Use the product API to manage multiple subscription tiers or one-time purchase options.
  • Implement proper pagination when listing videos, customers, or collections — responses are paginated by default.

Anti-Patterns

  • Do not rely on client-side entitlement checks; always verify access on the server before generating embed tokens.
  • Do not hardcode product IDs across your app; fetch and cache them so pricing changes don't require code deployments.
  • Do not expose your OTT API key to the client; all API calls should be proxied through your server.
  • Do not ignore webhook delivery failures; configure retry logic and a dead-letter queue for missed events.
  • Do not assume all customers have active subscriptions; always check the plan field or product entitlements before granting access.

Install this skill directly: skilldb add video-services-skills

Get CLI access →