Skip to content

Repository files navigation

Pug API Client for Ruby

A Ruby client library for the Pug Video API - a modern video management platform for creating, managing, and delivering video content at scale.

This gem provides an intuitive, object-oriented interface for working with video resources, livestreams, namespaces, campaigns, and more. It handles authentication, pagination, and API communication so you can focus on building great video applications.

What Can You Do With Pug?

  • Manage video resources (upload, update, delete, retrieve)
  • Create and manage livestreams and campaigns
  • Organize content with namespaces
  • Generate signed upload URLs
  • Execute video commands (clipping, processing, etc.)
  • Track video metadata and labels

Quick Start

Get up and running in 30 seconds:

# 1. Install the gemgeminstallpug-client# 2. Set your credentials and namespaceexportPUG_CLIENT_ID=your_client_idexportPUG_CLIENT_SECRET=your_client_secretexportPUG_NAMESPACE=my-videos# 3. Use the clientrequire'pug_client'# Namespace is REQUIRED during initializationclient=PugClient::Client.new(namespace: ENV['PUG_NAMESPACE'])client.authenticate!# Access your configured namespacenamespace=client.namespaceputs"Namespace: #{namespace.id}"# Iterate videos lazily (uses default namespace)client.videos.eachdo |video|
puts"Video #{video.id}: #{video.metadata[:title]}"end# Get a specific video and update itvideo=client.video('video-123')video.metadata[:labels][:status]='ready'video.metadata[:labels][:featured]=truevideo.save# Auto-generates JSON Patch

Table of Contents

Installation

Add this line to your application's Gemfile:

gem'pug-client'

Or install it yourself:

gem install pug-client

API Compatibility

This client library is designed for and tested against Pug Video API version 0.4.0.

PugClient::API_VERSION# => "0.4.0"

The library includes comprehensive integration tests recorded against the staging API using VCR cassettes. These tests validate that the client works correctly with the specified API version and serve as both verification and documentation of the expected API behavior.

Testing Against Specific API Versions:

Integration tests are organized by API version in spec/integration/api_v0.4.0/. When the API is updated, we maintain separate test suites and VCR recordings for each version to ensure compatibility and catch breaking changes.

For more information about API versions and compatibility, see the Pug Video API documentation.

Basic Usage

Here's a complete example showing the resource-based API:

require'pug_client'# 1. Create a client with namespace (REQUIRED)client=PugClient::Client.new(namespace: 'my-videos',client_id: ENV['PUG_CLIENT_ID'],client_secret: ENV['PUG_CLIENT_SECRET'])# 2. Authenticate (gets an OAuth access token)client.authenticate!# 3. Work with your configured namespacenamespace=client.namespace# Uses default namespaceputs"Namespace: #{namespace.id}"puts"Created at: #{namespace.created_at}"# Or access a different namespaceother_namespace=client.namespace('other-videos')# 4. Work with videos (uses default namespace)video=client.video('video-123')puts"Video: #{video.id}"# Or override namespace for specific callsvideo=client.video('video-456',namespace: 'other-videos')# 5. Update video naturally with dirty trackingvideo.metadata[:labels][:status]='processed'video.metadata[:labels][:featured]=truevideo.save# Automatically generates JSON Patch operations# 6. Create a clip from videoclip=video.clip(start_time: 5000,duration: 30000,metadata: {labels: {type: 'highlight'}})puts"Created clip: #{clip.id}"# 7. Upload a file (MP4 only currently)File.open('video.mp4','rb')do |file|
video.upload(file,filename: 'video.mp4')endvideo.wait_until_ready(timeout: 600)puts"Video ready: #{video.playback_urls}"# 8. Iterate videos lazily (fetches pages on-demand, uses default namespace)client.videos.eachdo |video|
puts"Video: #{video.id}"end# Or get first N videosrecent_videos=client.videos.first(10)# Override namespace for listingsother_videos=client.videos(namespace: 'other-videos').first(10)# 9. Delete videovideo.delete

Authentication

The client uses Auth0's client credentials flow for machine-to-machine authentication. You'll need API credentials (client ID and secret) from your Pug account.

Three Ways to Provide Credentials

1. Environment Variables (Recommended for Production)

# Set in your environment:# export PUG_CLIENT_ID=your_client_id# export PUG_CLIENT_SECRET=your_client_secret# export PUG_NAMESPACE=my-videosclient=PugClient::Client.new(namespace: ENV['PUG_NAMESPACE']# Required)client.authenticate!

2. Pass Directly (Good for Testing)

client=PugClient::Client.new(namespace: 'my-videos',# Requiredclient_id: 'your_client_id',client_secret: 'your_client_secret')client.authenticate!

3. Global Configuration (For Singleton Pattern)

PugClient.configuredo |c|
c.namespace='my-videos'# Requiredc.client_id='your_client_id'c.client_secret='your_client_secret'end# Now use module-level methodsPugClient.authenticate!namespace=PugClient.namespace# Uses configured namespace

Managing Authentication

# Check if authenticatedclient.authenticated?# => true/false# Check if token is expiredclient.token_expired?# => true/false# Auto-refresh token if needed (recommended before API calls)client.ensure_authenticated!# Manually refresh tokenclient.authenticate!

Working with Resources

Namespaces

Namespaces help organize your video content. Think of them as folders or projects. The resource-based API makes working with namespaces intuitive and object-oriented.

Important: A namespace ID is required when creating a client and serves as the default for all operations. You'll use an existing namespace that's been set up for your account.

# Create client with default namespace (REQUIRED - use existing namespace ID)client=PugClient::Client.new(namespace: 'my-videos',# Your existing namespace IDclient_id: ENV['PUG_CLIENT_ID'],client_secret: ENV['PUG_CLIENT_SECRET'])# Get your configured namespace (no ID needed)namespace=client.namespaceputsnamespace.id# => "my-videos"putsnamespace.metadata# Get a different namespace by IDother=client.namespace('other-videos')# Update namespace metadata (with automatic dirty tracking)namespace.metadata[:labels][:status]='active'namespace.metadata[:annotations]={project: 'v2'}namespace.save# Generates and sends JSON Patch automatically# Reload from API (discards unsaved changes)namespace.reloadputsnamespace.metadata# List all namespaces you have access to (returns lazy enumerator)client.namespaces.eachdo |ns|
puts"Namespace: #{ns.id}"end# List user's namespacesclient.user_namespaces.first(10)

Videos

Videos are the core resource. Each video belongs to a namespace and uses the powerful resource-based API for natural, Ruby-idiomatic operations.

Two ways to work with videos:

  1. Through the client (uses configured default namespace)
  2. Through a namespace object (uses that namespace's ID)
# Setup: Client with default namespaceclient=PugClient::Client.new(namespace: 'my-videos',client_id: ENV['PUG_CLIENT_ID'],client_secret: ENV['PUG_CLIENT_SECRET'])# Way 1: Use client directly (uses default namespace 'my-videos')video=client.video('video-123')putsvideo.id# Override namespace for specific callsvideo=client.video('video-456',namespace: 'other-videos')# Way 2: Use namespace objectnamespace=client.namespacevideo=namespace.video('video-123')# Same as client.video('video-123')# Update video metadata (automatic dirty tracking + JSON Patch)video.metadata[:labels][:status]='processed'video.metadata[:labels][:featured]=truevideo.save# Auto-generates: [# {op: 'add', path: '/attributes/metadata/labels/status', value: 'processed'},# {op: 'add', path: '/attributes/metadata/labels/featured', value: true}# ]# Upload a video file (MP4 only currently)File.open('video.mp4','rb')do |file|
video.upload(file,filename: 'video.mp4')end# Wait for video processing to completevideo.wait_until_ready(timeout: 600,interval: 5)puts"Video ready! Playback URLs: #{video.playback_urls}"# Create a clip from a video (returns NEW video resource)clip=video.clip(start_time: 10000,# Start at 10 seconds (milliseconds)duration: 30000,# 30 second clipmetadata: {labels: {type: 'highlight'}})puts"Created clip: #{clip.id}"# You can work with the clip like any other videoclip.metadata[:labels][:featured]=trueclip.save# Get the parent namespace of a videoparent=video.namespaceputs"Video belongs to: #{parent.id}"# Iterate videos (both approaches work)client.videos.eachdo |video| # Uses default namespaceputs"Video #{video.id}: #{video.started_at}"endnamespace.videos.eachdo |video| # Uses namespace's IDputs"Video #{video.id}: #{video.started_at}"end# Override namespace for listingsclient.videos(namespace: 'other-videos').each{ |v| putsv.id}# Get first N videos efficientlyrecent_videos=client.videos.first(10)# Force eager loading if neededall_videos=client.videos.to_a# Filter videos (still lazy)ready_videos=client.videos.select{ |v| v.status == 'ready'}# Delete a videovideo.delete# Reload video from API (discards local changes)video.reload

Read-Only Attributes

Some attributes cannot be modified after creation:

Namespace:id, created_at, updated_atVideo:id, created_at, updated_at, duration, renditions, playback_urls, thumbnail_url

video.id='new-id'# Raises ValidationErrorvideo.duration=90000# Raises ValidationError# But you can read themputsvideo.durationputsvideo.playback_urls

Resource API Features

Lazy Enumeration

Resource collections use Ruby's Enumerator pattern for efficient, on-demand pagination:

# Fetches pages as you iterate (memory efficient for large datasets)namespace.videos.eachdo |video|
putsvideo.idbreakifvideo.id == 'target-id'# Stops fetchingend# Get first N (only fetches enough pages)recent=namespace.videos.first(10)# Full Enumerable interfaceids=namespace.videos.map(&:id)featured=namespace.videos.select{ |v| v.metadata[:labels][:featured]}count=namespace.videos.count# Note: fetches all pages# Force eager loading when neededall_videos=namespace.videos.to_a

Dirty Tracking

Resources automatically track changes and generate JSON Patch operations:

video=client.video('my-namespace','video-123')# Make multiple changesvideo.metadata[:labels][:status]='ready'video.metadata[:labels][:reviewed]=truevideo.metadata[:annotations]={reviewer: 'john@example.com'}# Check if changedvideo.changed?# => true# Save sends JSON Patch automaticallyvideo.save# Generates:# [# {op: 'add', path: '/attributes/metadata/labels/status', value: 'ready'},# {op: 'add', path: '/attributes/metadata/labels/reviewed', value: true},# {op: 'add', path: '/attributes/metadata/annotations', value: {...}}# ]# After save, no longer dirtyvideo.changed?# => false# Reload discards unsaved changesvideo.metadata[:labels][:new]='value'video.reload# Local changes discarded, fresh from API

Attribute Translation

The API uses camelCase, but Ruby code uses snake_case:

# API returns: { data: { attributes: { startedAt: '...' } } }# Ruby sees:video.started_at# Automatic translation# When you update:video.metadata[:created_by]='user@example.com'# Sent to API as: { createdBy: 'user@example.com' }

Configuration

Environment Configuration

Pug provides preset configurations for production and staging environments.

Production (Default)

client=PugClient::Client.new(namespace: ENV['PUG_NAMESPACE'],# Requiredclient_id: ENV['PUG_CLIENT_ID'],client_secret: ENV['PUG_CLIENT_SECRET']# Uses production endpoints automatically)

Staging

# Option 1: Pass environment parameterclient=PugClient::Client.new(environment: :staging,namespace: ENV['PUG_NAMESPACE'],# Requiredclient_id: ENV['PUG_STAGING_CLIENT_ID'],client_secret: ENV['PUG_STAGING_CLIENT_SECRET'])# Option 2: Use global configurationPugClient.use_staging!PugClient.configuredo |c|
c.namespace=ENV['PUG_NAMESPACE']# Requiredc.client_id=ENV['PUG_STAGING_CLIENT_ID']c.client_secret=ENV['PUG_STAGING_CLIENT_SECRET']end

Custom/Local Development

For custom environments (local development, custom deployments), pass all endpoints explicitly:

client=PugClient::Client.new(namespace: 'test-namespace',# Requiredapi_endpoint: 'http://localhost:3000',auth_endpoint: 'http://localhost:3001/oauth/token',auth_audience: 'http://localhost:3000/',auth_grant_type: 'client_credentials',client_id: 'local_client_id',client_secret: 'local_client_secret')

Environment Defaults

SettingProductionStaging
API Endpointhttps://api.video.scorevision.comhttps://staging-api.video.scorevision.com
Auth Endpointhttps://fantagio.auth0.com/oauth/tokenhttps://fantagio-staging.auth0.com/oauth/token
Auth Audiencehttps://api.fantag.io/https://staging-api.fantag.io/

Configuration Options Reference

All available configuration options:

PugClient::Client.new(# Namespace (REQUIRED)namespace: 'my-videos',# Default namespace for all operations (required)# Environment (preset configurations)environment: :production,# :production (default) or :staging# Authentication (required)client_id: 'your_client_id',# OAuth client IDclient_secret: 'your_client_secret',# OAuth client secretaccess_token: 'manual_token',# Optional: skip auth flow with existing token# Endpoints (auto-set by environment, or specify manually)api_endpoint: 'https://...',# API base URLauth_endpoint: 'https://...',# Auth0 token endpointauth_audience: 'https://...',# Auth0 audienceauth_grant_type: 'client_credentials',# OAuth grant type# Paginationper_page: 10,# Default page size (default: 10)# HTTP Configurationconnection_options: {# Faraday optionsrequest: {open_timeout: 5,# Connection timeouttimeout: 10# Read timeout}})

Advanced Usage

Module-Level API

Use the module-level API for a singleton-style interface:

# Configure once (namespace is required)PugClient.configuredo |c|
c.namespace=ENV['PUG_NAMESPACE']# Requiredc.client_id=ENV['PUG_CLIENT_ID']c.client_secret=ENV['PUG_CLIENT_SECRET']end# Use anywherePugClient.authenticate!# Access configured namespacenamespace=PugClient.namespacevideos=namespace.videos.to_a# Or use client methods directly (uses configured namespace)videos=PugClient.videos.to_avideo=PugClient.video('video-123')

Error Handling

The gem provides specific error classes for better error handling:

beginvideo=client.video('non-existent')rescuePugClient::ResourceNotFound=>eputs"Video not found: #{e.resource_type}#{e.id}"rescuePugClient::NetworkError=>eputs"Network error: #{e.message}"rescuePugClient::AuthenticationError=>eputs"Auth error: #{e.message}"end# Namespace requirement validationbeginclient=PugClient::Client.new(client_id: ENV['PUG_CLIENT_ID'],client_secret: ENV['PUG_CLIENT_SECRET']# Missing required namespace parameter)rescueArgumentError=>eputse.message# => "namespace is required"end# Upload validationbeginvideo.upload(file,filename: 'video.avi',content_type: 'video/avi')rescuePugClient::ValidationError=>eputs"Invalid upload: #{e.message}"# => "Unsupported content type: video/avi. Currently only video/mp4 is supported."end# Wait timeoutbeginvideo.wait_until_ready(timeout: 60)rescuePugClient::TimeoutError=>eputs"Video processing took too long: #{e.message}"end

Low-Level HTTP Methods

For resources not yet supported by dedicated resource classes (livestreams, campaigns, etc.), use the low-level HTTP interface:

# GET requestresponse=client.get('livestreams')# POST request (with JSON:API formatted body)response=client.post('livestreams',{data: {type: 'livestreams',attributes: {title: 'My Livestream'}}})# PATCH request (JSON Patch format)response=client.patch("livestreams/123",[{op: 'replace',path: '/attributes/title',value: 'Updated Title'}])# DELETE requestclient.delete("livestreams/123")# Access response detailsputsclient.last_response.status# HTTP status codeputsclient.last_response.headers# Response headers

Troubleshooting

Authentication Errors

Problem:PugClient::AuthenticationError: Authentication failed

Solutions:

  • Verify your client_id and client_secret are correct
  • Check that you're using the right environment (production vs staging credentials)
  • Ensure your credentials haven't expired or been revoked
  • Try authenticating manually: client.authenticate!
# Debug authenticationbeginclient.authenticate!puts"Authenticated successfully!"rescuePugClient::AuthenticationError=>eputs"Auth failed: #{e.message}"puts"Using client_id: #{client.client_id}"puts"Auth endpoint: #{client.auth_endpoint}"end

Token Expiration

Problem: Getting 401 errors after initial authentication

Solution: Tokens expire after a certain time. Use ensure_authenticated! before API calls:

# Automatically refresh if expiredclient.ensure_authenticated!namespace=client.namespace('my-videos')# Or check manuallyifclient.token_expired?client.authenticate!end

Resource Not Found

Problem:PugClient::ResourceNotFound error

Solutions:

  • Verify the resource exists: client.videos.each { |v| puts v.id }
  • Check for typos in the resource ID
  • Ensure you have permission to access the resource
  • Verify you're using the correct environment (production vs staging)
  • Confirm you're using the correct namespace: client.namespace.id

Upload Errors

Problem: Upload fails or content type rejected

Solutions:

  • Only MP4 format is currently supported: content_type: 'video/mp4'
  • Ensure file is readable: File.open('video.mp4', 'rb')
  • Check file size and timeout settings
  • Verify video processing with wait_until_ready

Connection Timeouts

Problem: Requests timing out

Solution: Increase timeout values:

client=PugClient::Client.new(namespace: ENV['PUG_NAMESPACE'],client_id: ENV['PUG_CLIENT_ID'],client_secret: ENV['PUG_CLIENT_SECRET'],connection_options: {request: {open_timeout: 10,# Increase from defaulttimeout: 30# Increase from default}})

API Design

This gem uses a resource-based API design for core resources (Namespace, Video), providing:

  • Object-oriented interface - Resources are first-class Ruby objects
  • Lazy enumeration - Efficient iteration over large collections
  • Automatic dirty tracking - Changes tracked and converted to JSON Patch
  • Idiomatic Ruby - snake_case attributes, natural mutations, Enumerable support

Other resources (livestreams, campaigns, webhooks, etc.) currently use the client-centric API. These will be migrated to the resource-based pattern in future versions.

Requirements

  • Ruby >= 3.4
  • Faraday >= 2.14
  • Sawyer ~> 0.9

Contributing

Contributions are welcome! If you want to contribute to this gem, please see DEVELOPING.md for development setup, architecture details, and guidelines for adding new features.

License

This project is licensed under the MIT License - see the gemspec for details.

Links

About

A mirror of Pug API Client for ruby. PRs should go to our private repository.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages