Video Management
Video Management API
Manage your video library with comprehensive endpoints for listing, retrieving details, and deleting videos. HesedVid provides powerful tools for organizing and maintaining your video content at scale.
Overview
The Video Management API provides:
- Pagination: Efficient handling of large video libraries
- Filtering: Search and filter videos by status, date, and metadata
- Detailed Information: Complete video metadata and encoding details
- Status Tracking: Real-time processing status updates
- Soft Deletion: Safe deletion with data preservation for analytics
List Videos
/{orgID}/environments/{envID}/videos
Retrieve a paginated list of videos in an environment with comprehensive filtering and sorting options.
Authentication
All requests require authentication via API key:
X-Api-Key: hv_live_123456789abcdef...Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
orgID | string | Yes | Your organization ID (e.g., org_123456789) |
envID | string | Yes | Environment identifier (e.g., prod, staging, dev) |
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number (1-based) |
pageSize | integer | 20 | Items per page (max: 100) |
sortBy | string | created_at | Sort field: created_at, name, duration, status |
sortDir | string | desc | Sort direction: asc or desc |
status | string | - | Filter by status: transcoding, ready, failed |
search | string | - | Search in video names (case-insensitive) |
from | string | - | Start date filter (YYYY-MM-DD) |
to | string | - | End date filter (YYYY-MM-DD) |
Response
Success Response (200 OK)
{ "body": { "videos": [ { "id": "vid_AbCdEfGhIjKlMnOp", "name": "Product Demo 2024", "duration": "5:30", "durationSeconds": 330.5, "createdAt": "2024-01-15T10:30:00Z", "thumbnailUrl": "https://worker.hesedvid.com/t/vid_AbCdEfGhIjKlMnOp/320x180.jpg", "description": "Latest product demonstration video", "status": "ready", "served": "1.2K views", "allowPublicAccess": false, "videoQuality": "high" } ], "pagination": { "total": 156, "page": 1, "pageSize": 20, "totalPages": 8, "hasNext": true, "hasPrevious": false } }}Response Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique video identifier |
name | string | Video display name |
duration | string | Human-readable duration (e.g., “5:30”) |
durationSeconds | number | Duration in seconds |
createdAt | string | ISO 8601 timestamp |
thumbnailUrl | string | Thumbnail image URL |
description | string | Video description (if provided) |
status | string | Processing status |
served | string | View count summary |
allowPublicAccess | boolean | Public access setting |
videoQuality | string | Quality preset used |
Error Responses
| Status | Error | Description |
|---|---|---|
| 400 | invalid_page_size | Page size exceeds maximum (100) |
| 401 | unauthorized | Invalid or missing API key |
| 403 | forbidden | Insufficient permissions for environment |
| 404 | environment_not_found | Environment doesn’t exist |
Example: List Videos
# Basic listingcurl -X GET "https://api.hesedvid.com/v1/api/org_123/environments/prod/videos?page=1&pageSize=50" \ -H "X-Api-Key: hv_live_123456789abcdef..."
# With filteringcurl -X GET "https://api.hesedvid.com/v1/api/org_123/environments/prod/videos?status=ready&search=demo&sortBy=created_at&sortDir=desc" \ -H "X-Api-Key: hv_live_123456789abcdef..."const listVideos = async (options = {}) => { const params = new URLSearchParams({ page: options.page || 1, pageSize: options.pageSize || 20, sortBy: options.sortBy || 'created_at', sortDir: options.sortDir || 'desc', ...options.filters });
const response = await fetch( `https://api.hesedvid.com/v1/api/org_123/environments/prod/videos?${params}`, { headers: { 'X-Api-Key': 'hv_live_123456789abcdef...' } } );
const { body } = await response.json(); return body;};
// List all videosconst allVideos = await listVideos();
// Filter by statusconst readyVideos = await listVideos({ filters: { status: 'ready' }});
// Search and sortconst searchResults = await listVideos({ filters: { search: 'demo' }, sortBy: 'name', sortDir: 'asc'});import requestsfrom datetime import datetime, timedelta
def list_videos(org_id, env_id, **filters): params = { 'page': filters.get('page', 1), 'pageSize': filters.get('pageSize', 20), 'sortBy': filters.get('sortBy', 'created_at'), 'sortDir': filters.get('sortDir', 'desc') }
# Add optional filters if 'status' in filters: params['status'] = filters['status'] if 'search' in filters: params['search'] = filters['search'] if 'from' in filters: params['from'] = filters['from'] if 'to' in filters: params['to'] = filters['to']
response = requests.get( f'https://api.hesedvid.com/v1/api/{org_id}/environments/{env_id}/videos', params=params, headers={'X-Api-Key': 'hv_live_123456789abcdef...'} )
return response.json()['body']
# List all videosvideos = list_videos('org_123', 'prod')
# Filter by statusready_videos = list_videos('org_123', 'prod', status='ready')
# Search with date rangerecent_demos = list_videos( 'org_123', 'prod', search='demo', from='2024-01-01', to='2024-01-31')Get Video Details
/{orgID}/videos/{publicID}/details
Retrieve comprehensive information about a specific video, including encoding details, configuration, and processing status.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
orgID | string | Yes | Your organization ID |
publicID | string | Yes | Video public ID |
Response
Success Response (200 OK)
{ "body": { "id": "vid_AbCdEfGhIjKlMnOp", "publicID": "vid_AbCdEfGhIjKlMnOp", "name": "Product Demo 2024", "duration": "5:30", "durationSeconds": 330.5, "createdAt": "2024-01-15T10:30:00Z", "thumbnailUrl": "https://worker.hesedvid.com/t/vid_AbCdEfGhIjKlMnOp/1280x720.jpg", "description": "Latest product demonstration showcasing new features", "served": "1.2K views", "status": { "ready": true, "transcoding": false, "failed": false },
// Configuration "videoQuality": "high", "allowPublicAccess": false, "maxEdgeLength": 1080, "maxFrameRate": 30, "pixelFormat": "yuv420p", "rateControlMode": "variableBitrate", "twoPassVBREncoding": false, "compressionRatio": "veryfast", "audioCodec": "aac",
// Encoding ladder "encodings": [ { "key": "1080p", "shortEdgePixels": 1080, "shortEdgeIsWidth": false, "videoStreamBitrate": 5000, "audioStreamBitrate": 128, "audioFormat": "aac", "maxFrameRate": 30, "pixelFormat": "yuv420p", "rateControlMode": "variableBitrate", "twoPassVBREncoding": false, "compressionRatio": "veryfast" }, { "key": "720p", "shortEdgePixels": 720, "shortEdgeIsWidth": false, "videoStreamBitrate": 2500, "audioStreamBitrate": 128, "audioFormat": "aac", "maxFrameRate": 30, "pixelFormat": "yuv420p", "rateControlMode": "variableBitrate", "twoPassVBREncoding": false, "compressionRatio": "veryfast" }, { "key": "480p", "shortEdgePixels": 480, "shortEdgeIsWidth": false, "videoStreamBitrate": 1000, "audioStreamBitrate": 96, "audioFormat": "aac", "maxFrameRate": 30, "pixelFormat": "yuv420p", "rateControlMode": "variableBitrate", "twoPassVBREncoding": false, "compressionRatio": "veryfast" } ] }}Response Fields
| Field | Type | Description |
|---|---|---|
id | string | Internal video identifier |
publicID | string | Public video identifier (same as id) |
name | string | Video display name |
duration | string | Human-readable duration |
durationSeconds | number | Duration in seconds |
createdAt | string | ISO 8601 timestamp |
thumbnailUrl | string | Best available thumbnail URL |
description | string | Video description |
served | string | View count summary |
status | object | Processing status flags |
videoQuality | string | Quality preset used |
allowPublicAccess | boolean | Public access setting |
maxEdgeLength | integer | Maximum resolution setting |
maxFrameRate | number | Maximum frame rate setting |
pixelFormat | string | Pixel format used |
rateControlMode | string | Rate control mode |
twoPassVBREncoding | boolean | Two-pass encoding setting |
compressionRatio | string | Compression preset |
audioCodec | string | Audio codec used |
encodings | array | Available quality levels |
Error Responses
| Status | Error | Description |
|---|---|---|
| 401 | unauthorized | Invalid or missing API key |
| 403 | forbidden | Insufficient permissions for video |
| 404 | video_not_found | Video doesn’t exist or is deleted |
Example: Get Video Details
curl -X GET "https://api.hesedvid.com/v1/api/org_123/videos/vid_AbCdEfGhIjKlMnOp/details" \ -H "X-Api-Key: hv_live_123456789abcdef..."const getVideoDetails = async (videoID) => { const response = await fetch( `https://api.hesedvid.com/v1/api/org_123/videos/${videoID}/details`, { headers: { 'X-Api-Key': 'hv_live_123456789abcdef...' } } );
const { body } = await response.json(); return body;};
const videoDetails = await getVideoDetails('vid_AbCdEfGhIjKlMnOp');
if (videoDetails.status.ready) { console.log('Video is ready for playback'); console.log('Available qualities:', videoDetails.encodings.map(e => e.key)); console.log('Duration:', videoDetails.duration); console.log('Thumbnail:', videoDetails.thumbnailUrl);}import requests
def get_video_details(video_id): response = requests.get( f'https://api.hesedvid.com/v1/api/org_123/videos/{video_id}/details', headers={'X-Api-Key': 'hv_live_123456789abcdef...'} )
return response.json()['body']
video_details = get_video_details('vid_AbCdEfGhIjKlMnOp')
if video_details['status']['ready']: print(f"Video: {video_details['name']}") print(f"Duration: {video_details['duration']}") print(f"Available qualities: {[e['key'] for e in video_details['encodings']]}") print(f"Thumbnail: {video_details['thumbnailUrl']}")Tip
The encoding ladder shows all available quality levels for the video. Use this information to build quality selectors in your player UI and optimize bandwidth usage.
Delete Video
/{orgID}/videos/{videoID}
Permanently delete a video and its associated files. This action uses HesedVid’s enhanced deletion system that preserves analytics data while removing content.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
orgID | string | Yes | Your organization ID |
videoID | string | Yes | Video public ID |
Request Body (Optional)
{ "reason": "user_request"}| Field | Type | Required | Description |
|---|---|---|---|
reason | string | No | Deletion reason for audit trail |
Deletion Reasons
| Reason | Description |
|---|---|
user_request | User initiated deletion |
policy_violation | Content policy violation |
copyright_claim | Copyright infringement |
expired_content | Content past retention period |
admin_action | Administrative deletion |
Response
Success Response (200 OK)
{ "body": { "status": "deleted", "deletedAt": "2024-01-15T11:45:00Z", "message": "Video deleted successfully. Content will be removed within 24 hours." }}| Field | Type | Description |
|---|---|---|
status | string | Deletion status |
deletedAt | string | ISO 8601 timestamp of deletion |
message | string | Confirmation message |
Error Responses
| Status | Error | Description |
|---|---|---|
| 400 | already_deleted | Video is already deleted |
| 401 | unauthorized | Invalid or missing API key |
| 403 | forbidden | Insufficient permissions for video |
| 404 | video_not_found | Video doesn’t exist |
Example: Delete Video
curl -X DELETE "https://api.hesedvid.com/v1/api/org_123/videos/vid_AbCdEfGhIjKlMnOp" \ -H "X-Api-Key: hv_live_123456789abcdef..." \ -H "Content-Type: application/json" \ -d '{"reason": "user_request"}'const deleteVideo = async (videoID, reason = 'user_request') => { const response = await fetch( `https://api.hesedvid.com/v1/api/org_123/videos/${videoID}`, { method: 'DELETE', headers: { 'X-Api-Key': 'hv_live_123456789abcdef...', 'Content-Type': 'application/json' }, body: JSON.stringify({ reason }) } );
const { body } = await response.json(); return body;};
// Delete with confirmationconst confirmDelete = async (videoID) => { if (confirm('Are you sure you want to delete this video? This action cannot be undone.')) { try { const result = await deleteVideo(videoID); console.log('Video deleted:', result.message); } catch (error) { console.error('Delete failed:', error); } }};
await confirmDelete('vid_AbCdEfGhIjKlMnOp');import requests
def delete_video(video_id, reason='user_request'): response = requests.delete( f'https://api.hesedvid.com/v1/api/org_123/videos/{video_id}', headers={ 'X-Api-Key': 'hv_live_123456789abcdef...', 'Content-Type': 'application/json' }, json={'reason': reason} )
return response.json()['body']
# Delete with confirmationdef confirm_delete(video_id): confirm = input(f"Delete video {video_id}? (y/N): ") if confirm.lower() == 'y': try: result = delete_video(video_id) print(f"Video deleted: {result['message']}") except Exception as e: print(f"Delete failed: {e}") else: print("Deletion cancelled")
confirm_delete('vid_AbCdEfGhIjKlMnOp')Caution
Video deletion is permanent. The video is immediately hidden from all interfaces, and files are removed within 24 hours. Analytics data is preserved for billing and reporting purposes.
Video Status Management
Status Types
Videos progress through several states during their lifecycle:
| Status | Description | Playable | Duration |
|---|---|---|---|
transcoding | Video is being processed | No | 2-15 minutes |
ready | Video is ready for playback | Yes | Indefinite |
failed | Processing failed | No | Indefinite |
Status Transitions
graph LR A[Uploaded] --> B[Transcoding] B --> C[Ready] B --> D[Failed] C --> E[Deleted] D --> E D --> F[Retry Processing] F --> BStatus Polling
For applications that need real-time status updates:
const pollVideoStatus = async (videoID, maxAttempts = 30) => { for (let attempt = 0; attempt < maxAttempts; attempt++) { const details = await getVideoDetails(videoID);
if (details.status.ready) { console.log('Video is ready!'); return details; } else if (details.status.failed) { console.error('Video processing failed'); return null; }
// Wait before next poll (exponential backoff) const delay = Math.min(1000 * Math.pow(2, attempt), 10000); await new Promise(resolve => setTimeout(resolve, delay)); }
console.log('Status polling timeout'); return null;};Advanced Filtering and Search
Filter by Status
// Get only ready videosconst readyVideos = await listVideos({ filters: { status: 'ready' }});
// Get videos currently processingconst processingVideos = await listVideos({ filters: { status: 'transcoding' }});Search by Name
// Search for videos containing "demo"const demoVideos = await listVideos({ filters: { search: 'demo' }});
// Case-insensitive searchconst productVideos = await listVideos({ filters: { search: 'product' }});Date Range Filtering
// Videos from last monthconst lastMonth = new Date();lastMonth.setMonth(lastMonth.getMonth() - 1);
const recentVideos = await listVideos({ filters: { from: lastMonth.toISOString().split('T')[0], to: new Date().toISOString().split('T')[0] }});Combined Filters
// Complex filtering exampleconst filteredVideos = await listVideos({ filters: { status: 'ready', search: 'demo', from: '2024-01-01', to: '2024-01-31' }, sortBy: 'created_at', sortDir: 'desc', pageSize: 50});Pagination Best Practices
Efficient Pagination
const getAllVideos = async (filters = {}) => { const allVideos = []; let page = 1; let hasMore = true;
while (hasMore) { const result = await listVideos({ page, pageSize: 100, // Maximum page size ...filters });
allVideos.push(...result.videos); hasMore = result.pagination.hasNext; page++; }
return allVideos;};Infinite Scroll Implementation
const InfiniteVideoList = () => { const [videos, setVideos] = useState([]); const [loading, setLoading] = useState(false); const [hasMore, setHasMore] = useState(true); const [page, setPage] = useState(1);
const loadMoreVideos = async () => { if (loading || !hasMore) return;
setLoading(true); try { const result = await listVideos({ page, pageSize: 20 });
setVideos(prev => [...prev, ...result.videos]); setHasMore(result.pagination.hasNext); setPage(prev => prev + 1); } catch (error) { console.error('Failed to load videos:', error); } finally { setLoading(false); } };
// Load more when user scrolls to bottom useEffect(() => { const handleScroll = () => { if (window.innerHeight + document.documentElement.scrollTop >= document.documentElement.offsetHeight - 1000) { loadMoreVideos(); } };
window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); }, [page, loading, hasMore]);
return ( <div> {videos.map(video => ( <VideoCard key={video.id} video={video} /> ))} {loading && <div>Loading...</div>} </div> );};Error Handling
Comprehensive Error Handling
const handleVideoAPIError = (error, operation) => { switch (error.status) { case 400: console.error(`Bad request for ${operation}:`, error.detail); break; case 401: console.error('Authentication failed - check API key'); break; case 403: console.error('Insufficient permissions'); break; case 404: console.error('Video or environment not found'); break; case 429: console.error('Rate limit exceeded - retry later'); break; case 500: console.error('Server error - try again'); break; default: console.error(`Unexpected error during ${operation}:`, error); }};
// Usagetry { const videos = await listVideos();} catch (error) { handleVideoAPIError(error, 'list videos');}Retry Logic
const retryOperation = async (operation, maxRetries = 3) => { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await operation(); } catch (error) { if (attempt === maxRetries - 1) throw error;
// Exponential backoff const delay = Math.pow(2, attempt) * 1000; await new Promise(resolve => setTimeout(resolve, delay)); } }};
// Usageconst videos = await retryOperation(() => listVideos());Best Practices
Performance Optimization
Implement pagination: Always paginate when listing videos to avoid timeouts with large libraries. Use the maximum page size (100) when possible.
Cache video details: Video metadata doesn’t change often. Cache details to reduce API calls and improve performance.
Use appropriate filters: Apply filters on the server side rather than filtering large datasets client-side.
Implement efficient search: Use the search parameter for name-based filtering rather than downloading all videos.
User Experience
Handle status polling: When checking processing status, implement exponential backoff to avoid rate limits and reduce server load.
Confirm deletions: Always confirm with users before deleting videos as the action is irreversible.
Provide feedback: Show loading states, error messages, and success confirmations for all operations.
Implement infinite scroll: For large video libraries, use infinite scroll or virtual scrolling to improve performance.
Security Considerations
Validate permissions: Always check that users have permission to access videos before displaying them.
Sanitize search terms: Validate and sanitize search parameters to prevent injection attacks.
Rate limit client requests: Implement client-side rate limiting to avoid hitting API limits.
Audit deletions: Log all deletion operations with reasons for compliance and debugging.
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| Empty video list | Wrong environment ID | Verify environment ID exists |
| Slow loading | Large page size | Reduce page size or implement pagination |
| Search not working | Invalid search syntax | Use simple text search, avoid special characters |
| Deletion fails | Insufficient permissions | Check API key permissions |
| Status stuck | Processing error | Check video details for error messages |
Debug Mode
Enable detailed logging for troubleshooting:
const debugListVideos = async (options = {}) => { console.log('Requesting videos with options:', options);
const startTime = Date.now(); const result = await listVideos(options); const duration = Date.now() - startTime;
console.log(`Request completed in ${duration}ms`); console.log(`Retrieved ${result.videos.length} videos`); console.log(`Total available: ${result.pagination.total}`);
return result;};Support
For additional help:
- Check our health endpoint
GET /healthfor service issues - Review error codes for detailed explanations
- Contact support with your organization ID and video ID for assistance