This document outlines the V2 of the HTTP interface that Panda exposes to other applications.
We provide two API end-points, one for US and one for EU.
Video is a restfull resource representing the original media.
It describes all major metadata of a media file, it's size and it's original filename.
It contains a status attribute representing the uploading process of media to S3.
status is set to success when the media has been successfully uploaded to S3.
Careful: It does not mean that it's encodings have been encoded.
GET /videos.jsonReturns a collection of Video objects.
status: one of 'success', 'fail', 'processing'. Filter by status
page: default 1
per_page: default 100
[{
"id":"d891d9a45c698d587831466f236c6c6c",
"original_filename":"test.mp4",
"extname":".mp4",
"path":"d891d9a45c698d587831466f236c6c6c",
"video_codec":"h264",
"audio_codec":"aac",
"height":240,
"width":300,
"fps":29,
"duration":14000,
"file_size": 39458349,
"created_at":"2009/10/13 19:11:26 +0100",
"updated_at":"2009/10/13 19:11:26 +0100"
}, ... ]
GET /videos/:id.jsonid: ID of the Video object
GET /videos/d891d9a45c698d587831466f236c6c6c.json
{
"id":"d891d9a45c698d587831466f236c6c6c",
"original_filename":"test.mp4",
"extname":".mp4",
"path":"d891d9a45c698d587831466f236c6c6c",
"video_codec":"h264",
"audio_codec":"aac",
"height":240,
"width":300,
"fps":29,
"duration":14000,
"file_size":39458349,
"created_at":"2009/10/13 19:11:26 +0100",
"updated_at":"2009/10/13 19:11:26 +0100"
}
GET /videos/:id/encodings.jsonid: ID of the Video object
status: one of 'success', 'fail', 'processing'. Filter by status profile_id: filter by profile_id profile_name: filter by profile_name
GET /videos/d891d9a45c698d587831466f236c6c6c/encodings.json
[{
"id":"2f8760b7e0d4c7dbe609b5872be9bc3b",
"video_id":"d891d9a45c698d587831466f236c6c6c",
"extname":".mp4",
"path":"2f8760b7e0d4c7dbe609b5872be9bc3b",
"profile_id":"40d9f8711d64aaa74f88462e9274f39a",
"profile_name":"h264",
"status":"success",
"encoding_progress":99,
"height":240,
"width":300,
"file_size":29458349,
"started_encoding_at":"2009/10/13 21:28:45 +0000",
"encoding_time":9000,
"files":["2f8760b7e0d4c7dbe609b5872be9bc3b.mp4"],
"created_at":"2009/10/13 20:58:29 +0000",
"updated_at":"2009/10/13 21:30:34 +0000"
}, ... ]
GET /videos/:id/metadata.jsonJust after the media is uploaded, we analyze and store the metadata that we can extract from it.
GET /videos/d891d9a45c698d587831466f236c6c6c/metadata.json
{
"image_height":208,
"audio_format":"mp4a",
"selection_time":"0 s",
"track_layer":0,
"poster_time":"0 s",
"video_frame_rate":30.0,
"duration":"19.35 s",
"media_create_date":"Tue Jan 28 20:59:44 +0000 1913",
"audio_sample_rate":48000,
"compressor_id":"avc1",
"graphics_mode":"srcCopy",
"audio_channels":2,
"media_header_version":0,
"track_modify_date":"Tue Jan 28 20:59:39 +0000 1913",
"preferred_volume":"100.00%",
"mime_type":"video/mp4",
"file_size":"1435 kB",
"create_date":"Tue Jan 28 20:59:39 +0000 1913",
"rotation":0
....
}
POST /videos.jsonIf you use Panda in your web application, you'll probably want to use our Panda Uploader to upload video files from a web page. Otherwise, you can use this API to upload videos.
NOTE: when creating the authorisation signature for this request do not include the file parameter.
One of the following parameters is required:
file: the file that is being uploaded
source_url: url of a remote video file
If you use the file parameter, make sure that your HTTP request uses an appropriate Content-Type, such as multipart/form-data.
profiles: comma-separated list of profile names or IDs to be used during encoding. Alternatively, specify "none" so no encodings are created yet
path_format: represents the complete video path without the extension name. It can be constructed using some provided keywords.
payload: arbitrary string stored along the Video object.
The following keywords can be combined to create a path format.
:id : Id of the video or the encoding.
:video_id : Id of the video.
:original : Original filename of the uploaded video
:date : Date the video was uploaded. ('2009-10-09')
:profile : Profile name used by the encoding ('original' is used when the file is the original video)
:type : Video type, 'original' or 'encodings'
:resolution : Resolution of the video or the encoding. ('480x340')
e.g. "my-path/:video_id/:profile/:id"
By default path_format is set to ":id"
The payload is an arbitrary text of length 256 or shorter that you can store along the Video. It is typically used to retain an association with one of your own DB record ID.
POST /videos.json
profiles: "h264,webm"
path_format: "my-path/:id"
payload: "2456"
{
"id":"d891d9a45c698d587831466f236c6c6c",
"original_filename":"test.mp4",
"extname":".mp4",
"path":"my-path/d891d9a45c698d587831466f236c6c6c",
"video_codec":"h264",
"audio_codec":"aac",
"height":240,
"width":300,
"fps":29,
"duration":14000,
"file_size": 29458349,
"created_at":"2009/10/13 19:11:26 +0100",
"updated_at":"2009/10/13 19:11:26 +0100"
}
POST /videos/upload.jsonBefore uploading a file to panda, you will need to create a session for each particular files. In return you get a unique location for this file.
Our upload API supports 2 ways of uploading a file: resumable (also known as streaming) and multipart upload.
POST /videos/upload.json
file_size: 805301
file_name: panda.mp4
location: http://vm-37gup6.upload.pandastream.com/upload/session?id=abcdefgh
id: abcdefgh
status: 201
file_size: Size in bytes of the video
file_name: File name of the video
use_all_profiles: Default is false
profiles: comma-separated list of profile names or IDs to be used during encoding. Alternatively, specify "none" so no encodings are created yet
path_format: represents the complete video path without the extension name. It can be constructed using some provided keywords.
payload: arbitrary string stored along the Video object.
DELETE /videos/:id.jsonDELETE /videos/d891d9a45c698d587831466f236c6c6c.json
status: 200
Each encoding represents a version of a video encoded with the settings defined in the profile used. When an encoding is created (either automatically when a video is uploaded, or using the POST method below) it is immediately placed on your encoding queue.
If the original video has been successfully uploaded to your s3 bucket, the video status will be set to 'success' If the video has failed, the encoding will fail as well.
If the encoding was successful, you can download it or play it using the following convention $path$$extname$. Both values can be retrieved using the API.
As well, 7 screenshots will then be generated and uploaded to the S3 bucket with the name using the following convention: $path$_$screenshot_number$.jpg e.g. (my-path/2f8760b7e0d4c7dbe609b5872be9bc3b_1.jpg).
If the encoding fails, this is usually because there was either an invalid profile being used, or the origin video is corrupted or not recognised. In this case, a logfile will be uploaded to S3 (with the filename $path$.log) containing some debug information which can help in correcting issues caused by an invalid profile.
GET /encodings.jsonstatus: one of 'success', 'fail', 'processing'. Filter by status
profile_id: filter by profile_id
profile_name: filter by profile_name
video_id: filter by video_id
GET /encoding.json?status=success
[{
"id":"2f8760b7e0d4c7dbe609b5872be9bc3b",
"video_id":"d891d9a45c698d587831466f236c6c6c",
"extname":".mp4",
"path":"2f8760b7e0d4c7dbe609b5872be9bc3b",
"profile_id":"40d9f8711d64aaa74f88462e9274f39a",.
"profile_name":"h264",
"status":"success",
"encoding_progress":99,
"height":240,
"width":300,
"started_encoding_at":"2009/10/13 21:28:45 +0000",
"encoding_time":9000,
"files":["2f8760b7e0d4c7dbe609b5872be9bc3b.mp4"],
"created_at":"2009/10/13 20:58:29 +0000",
"updated_at":"2009/10/13 21:30:34 +0000"
}, ... ]
GET /encodings/:id.jsonGET /encodings/2f8760b7e0d4c7dbe609b5872be9bc3b.json
{
"id":"2f8760b7e0d4c7dbe609b5872be9bc3b",
"video_id":"d891d9a45c698d587831466f236c6c6c",
"extname":".mp4",
"path":"2f8760b7e0d4c7dbe609b5872be9bc3b",
"profile_id":"40d9f8711d64aaa74f88462e9274f39a",
"profile_name":"h264",
"status":"success",
"encoding_progress":99,
"height":240,
"width":300,
"started_encoding_at":"2009/10/13 21:28:45 +0000",
"encoding_time":9000,
"files":["2f8760b7e0d4c7dbe609b5872be9bc3b.mp4"],
"created_at":"2009/10/13 20:58:29 +0000",
"updated_at":"2009/10/13 21:30:34 +0000"
}
POST /encodings.jsonCreate a new encoding for a video that already exists.
video_id: ID of existing video
profile_id: ID of existing profile
profile_name: Name of existing profile
POST /encodings.json
video_id: d891d9a45c698d587831466f236c6c6c
profile_name: h264
{
"id":"2f8760b7e0d4c7dbe609b5872be9bc3b",
"video_id":"d891d9a45c698d587831466f236c6c6c",
"extname":".mp4",
"path":"2f8760b7e0d4c7dbe609b5872be9bc3b",
"profile_id":"40d9f8711d64aaa74f88462e9274f39a",
"profile_name":"h264",
"status":"processing",
"encoding_progress":0,
"height":240,
"width":300,
"started_encoding_at":"",
"encoding_time":0,
"files":[],
"created_at":"2009/10/13 20:58:29 +0000",
"updated_at":"2009/10/13 21:30:34 +0000"
}
POST /encodings/:id/cancel.jsonCancel an encoding
POST /encodings/2f8760b7e0d4c7dbe609b5872be9bc3b/cancel.json
status: 200
POST /encodings/:id/retry.jsonRetry a failed encoding
POST /encodings/2f8760b7e0d4c7dbe609b5872be9bc3b/retry.json
status: 200
DELETE /encodings/:id.jsonDELETE /encodings/2f8760b7e0d4c7dbe609b5872be9bc3b.json
status: 200
GET /profiles.jsonGET /profiles.json
[{
"id":"40d9f8711d64aaa74f88462e9274f39a",
"title":"MP4 (H.264)",
"name": "h264",
"extname":".mp4",
"width":320,
"height":240,
"audio_bitrate": 128,
"video_bitrate": 500,
"aspect_mode": "letterbox",
"command":"ffmpeg -i $input_file$ -c:a libfaac $audio_bitrate$ -c:v libx264 $video_bitrate$ -preset medium $filters$ -y $output_file$",
"created_at":"2009/10/14 18:36:30 +0000",
"updated_at":"2009/10/14 19:38:42 +0000"
}, ... ]
GET /profiles/:id.jsonGET /profiles/40d9f8711d64aaa74f88462e9274f39a.json
{
"id":"40d9f8711d64aaa74f88462e9274f39a",
"title":"MP4 (H.264)",
"name": "h264",
"extname":".mp4",
"width":320,
"height":240,
"audio_bitrate": 128,
"video_bitrate": 500,
"aspect_mode": "letterbox",
"command":"ffmpeg -i $input_file$ -c:a libfaac $audio_bitrate$ -c:v libx264 $video_bitrate$ -preset medium $filters$ -y $output_file$",
"created_at":"2009/10/14 18:36:30 +0000",
"updated_at":"2009/10/14 19:38:42 +0000"
}
POST /profiles.jsonname: Unique Machine-readable name that will identify the profile.
Helpful later on for filtering encodings by profile
title: Human-readable name
extname: (example: '.mp4') File extension
width: (example: 1080) Width in pixels
height: (example: 720) Height in pixels
upscale: (default: true) Upscale the video resolution to match your profile
aspect_mode: (default: letterbox)
two_pass: (default: false)
video_bitrate: (example: 3000)
fps: (example: 29.97, default: not set) Null value copy the original fps
keyframe_interval (default: 250) Adds a key frame every 250 frames
keyframe_rate (example: 0.5) Adds a key frame every 2 seconds
audio_bitrate: (example: 128)
audio_sample_rate: (default: 44100)
audio_channels: (default: not set)
clip_length: (example: '00:20:00') Sets the clip's duration
clip_offset: (example: '00:00:10') Clip starts at a specific offset
watermark_url: Url of the watermark image
watermark_top,
watermark_bottom,
watermark_left,
watermark_right: (default: 0) Distance from the top/bottom/right/left of the video frame
Works like CSS
watermark_width,
watermark_height: (default: no resizing) Size of the watermark image
frame_count: (default: 7) Evenly spaced number of generated screenshots
Aspect Modes:
preserve: Original size and ratio is preserved.
constrain: Aspect ratio is maintained.
No black bars is added to your output.
letterbox: Aspect ratio is maintained.
Adds black bars to your output to match your profile frame height (above and below only).
pad: Aspect ratio is maintained.
Adds black bars to your output to match your profile frame size.
crop: Aspect ratio is maintained.
fills your profile frame size and crops the rest.
H264 preset:
h264_crf: (example: 23)
h264_profile: (example: 'baseline')
h264_level: (example: '3.1')
JPEG preset:
Only one of the following attributes can be set.
frame_count: (example: 7) Evenly spaced number of thumbnails
frame_offsets: (example: '2s, 10s, 250f, 400f') Array of offset (Frames or seconds),
frame_interval: (example: '1000f') Thumbnail interval (Frames or seconds)
preset_name
POST /profiles.json
preset_name: 'h264'
{
"id":"40d9f8711d64aaa74f88462e9274f39a",
"title": "H264 (MP4)",
"name": "h264",
"extname":".mp4",
"width":480,
"height":320,
"audio_bitrate": 128,
"video_bitrate": 500,
"preset_name": "h264",
"created_at":"2009/10/14 18:36:30 +0000",
"updated_at":"2009/10/14 19:38:42 +0000"
}
command: line break separated list of encoding commands to run.
extname: file extension (example: '.mp4')
POST /profiles.json
title: H264 normal quality
name: h264
extname: .mp4
width: 320
height: 240
audio_bitrate: 128
video_bitrate: 500
aspect_mode: pad
command: ffmpeg -i $input_file$ -c:a libfaac $audio_bitrate$ -c:v libx264 $video_bitrate$ -preset medium $filters$ -y $output_file$
{
"id":"40d9f8711d64aaa74f88462e9274f39a",
"title":"H264 normal quality",
"extname":".mp4",
"width":320,
"height":240,
"audio_bitrate":128,
"video_bitrate":500,
"aspect_mode" "pad",
"command":"ffmpeg -i $input_file$ -c:a libfaac $audio_bitrate$ -c:v libx264 $video_bitrate$ -preset medium $filters$ -y $output_file$",
"created_at":"2009/10/14 18:36:30 +0000",
"updated_at":"2009/10/14 19:38:42 +0000"
}
PUT /profiles/:id.jsonSame as POST /profiles.json but preset_name
PUT /profiles/40d9f8711d64aaa74f88462e9274f39a.json
title: The best custom profile
{
"id":"40d9f8711d64aaa74f88462e9274f39a",
"title":"The best custom profile",
"extname":".mp4",
"width":320,
"height":240,
"audio_bitrate":128,
"video_bitrate":500,
"command":"ffmpeg -i $input_file$ -c:a libfaac $audio_bitrate$ -c:v libx264 $video_bitrate$ -preset medium $filters$ -y $output_file$",
"created_at":"2009/10/14 18:36:30 +0000",
"updated_at":"2009/10/14 19:38:42 +0000"
}
DELETE /profiles/:id.jsonDELETE /profiles/40d9f8711d64aaa74f88462e9274f39a.json
status: 200
GET /clouds/:id.jsonGET /clouds/e122090f4e506ae9ee266c3eb78a8b67.json
{
"id": "e122090f4e506ae9ee266c3eb78a8b67",
"name": "my_first_cloud",
"s3_videos_bucket": "my-example-bucket",
"s3_private_access":false,
"url": "http://my-example-bucket.s3.amazonaws.com/",
"created_at": "2010/03/18 12:56:04 +0000",
"updated_at": "2010/03/18 12:59:06 +0000"
}
PUT /clouds/:id.jsonname
s3_videos_bucket
aws_access_key
aws_secret_key
PUT /clouds/e122090f4e506ae9ee266c3eb78a8b67.json
name: "my_first_cloud"
s3_videos_bucket: "my_own_bucket"
aws_access_key: XQwEwFR
aws_secret_key: XoSV2fV
{
"id": "e122090f4e506ae9ee266c3eb78a8b67",
"name": "my_first_cloud_updated",
"s3_videos_bucket": "my-example-bucket",
"s3_private_access": false
"created_at":"2010/03/18 12:56:04 +0000",
"url": "http://my-example-bucket.s3.amazonaws.com/",
"updated_at":"2010/03/18 12:59:06 +0000"
}
GET /notifications.jsonGET /notifications.json
{
"url": "null",
"events": {
"video_created": false,
"video_encoded": false,
"encoding_progress": false,
"encoding_completed": false
}
}
PUT /notifications.jsonPUT /notifications.json
url: "http://example.com/panda_notification"
events['video_encoded'] = true
{
"url": "http://example.com/panda_notification",
"events": {
"video_created": false,
"video_encoded": true,
"encoding_progress": false,
"encoding_completed": false
}
}
Encoding and Video have 2 special attributes when their status is set to 'fail'
error_class: Helps you figure out what happened and what to do next.
error_message: Gives you a more descriptive message of the error
The following table list all possible error classes:
S3Error
Whenever the system try to access your bucket and gets an bad response from S3.
VideoStatusInvalid
The original video is in error state and can not be processed. Check the videos error to know the reason (only applied for encodings).
CommandInvalid
The command of your custom profile is not correct. You should take a look at your encoding logfile.
EncodingError
The encoding has failed. You should take a look at your encoding logfile.
FileNotFound
Your original video file doesn't exist on S3.
DownloadFailed
Panda was not able to download the file from source_url.
UnexpectedError
Error is not expected from the system we will look at it.
FormatNotRecognised
Your file is not a video or audio file valid.
When there is an issue with a request, Panda will return the appropriate HTTP status code, along with a JSON object containing the error name and a message. (500 status returns an empty body). For more information on HTTP status codes, see https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
When there is an internal error a 500 status will be returned along with additional information in the message. Whenever a 500 error occurs our technical team is notified of the issue and will investigate the root of the problem immediately. If your experience a recurring issue, please submit a support ticket to support.pandastream.com
When the signature parameter of a request is not correct, the server returns a status code 401 and a descriptive message. If you receive this error please ensure that you are constructing and encoding the signature parameter as described in the API Authentication section below.
If you receive a Signatures expired message, that means your clock is out of sync. You can maintain the system time in synchronization with time servers using ntpd.
{"error" : "NotAuthorized", "message" : "Signatures do not match"}
{"error" : "NotAuthorized", "message" : "Signatures expired"}
A record with the id supplied could not be found.
{"error" : "RecordNotFound", "message" : "Couldn't find Video with ID=X"}
This error will be returned in two cases. Either because you have requested a response format that is not supported (currently only JSON is supported, so all urls must end in .json), or you have not submitted all of the required parameters to a method.
{"error" : "BadRequest", "message" : "Currently only .json is supported as a format"}
{"error" : "BadRequest", "message" : "All required parameters were not supplied: access_key, signature, timestamp"}
If you attempt to upload a file > 10MB with a sandbox account, the following error will be returned.
{"error" : "BadRequest", "message" : "File size limit for this account is set to 10485760 bytes"}
For examples of authentication implementation please refer to the Ruby gem and example PHP client.
The Panda API requires all requests must also be signed to ensure they are valid and authenticated. For GET and DELETE requests the additional parameters must be url encoded and added to the parameters in the url. When making a POST or PUT request they should be included in the usual parameters payload submitted.
The access_key and secret_key used to authenticate the request are provided when you sign up for your Panda account. Your keys can always be found by logging in to your account by visiting pandastream.com
A correctly signed request contains the following additional parameters:
access_key: Provided when you sign up for Panda
cloud_id: Provided when you sign up for Panda
timestamp: Current UTC time in iso8601 format
signature: HMAC signature generated as described below
The signature is generated using the following method:
1) Create a canonical_querystring by url encoding all of the parameters and the values, and joining them into one stringusing the = character to separate keys and their values, and the & character to separate the key value pairs.
All parameters and values have to be joined in the alphabetical order.
Make sure it encodes the given string according to ยป RFC 3986. (spaces are escaped using %20 and not a +)
A typical canonical_querystring might look as follows: access_key=85f8dbe6-b998-11de-82e1-001ec2b5c0e1&cloud_id=bd54547d152e91104d82c0a81e7d5ff2×tamp=2009-10-15T15%3A38%3A42%2B01%3A00 ... other parameters such as those in the POST request would also be added to this string.
2) Construct the string_to_sign by concatenating the uppercase HTTP request method (GET, POST, PUT or DELETE), hostname (api.pandastream.com or api-eu.pandastream.com), request uri (e.g. /videos.json) and canonical_querystring with newlines (\n).
The api version /v2 should not be part of the request uri
3) To generate the signature, first HMAC SHA256 encode the complete string_to_sign using your secret_key as the key. For example, using Ruby: hmac = HMAC::SHA256.new(secret_key) then hmac.update(string_to_sign)
4) Then take the binary digest of the hmac output and base 64 encode it. Make sure to remove any newline characters at the end. In Ruby you would do Base64.encode64(hmac.digest).chomp
5) The signature could be rejected by the server for the following reasons: the request is a POST request and the signature has already been used the request is POST /videos.json and the timestamp is expired for 30 minutes the request is not POST and the timestamp is expired for 5 minutes
Assume that we wish to get all videos we have uploaded to our cloud (US account)
cloud_id = '123456789'
access_key = 'abcdefgh'
secret_key = 'ijklmnop'
We first construct the request uri
api.pandastream.com/v2/videos.json
We send the following query parameters
access_key abcdefgh
cloud_id 123456789
timestamp 2011-03-01T15:39:10.260762Z
The signature is generated by signing the following string
GET\napi.pandastream.com\n/videos.json\naccess_key=abcdefgh&cloud_id=123456789×tamp=2011-03-01T15%3A39%3A10.260762Z
This should be signed by generating the HMAC SHA256 hex digest with secret key ijklmnop
kVnZs%2FNX13ldKPdhFYoVnoclr8075DwiZF0TGgIbMsc%3D
The api request then becomes
GET /videos.json
QUERY
access_key=abcdefgh&cloud_id=123456789×tamp=2011-03-01T15%3A39%3A10.260762Z&signature=kVnZs%2FNX13ldKPdhFYoVnoclr8075DwiZF0TGgIbMsc%3D
Here is the request and response made with curl
$ curl http://api.pandastream.com/v2/videos.json?access_key=abcdefgh&cloud_id=123456789×tamp=2011-03-01T15:39:10.260762Z&signature=kVnZs%2FNX13ldKPdhFYoVnoclr8075DwiZF0TGgIbMsc%3D
status: "200"
body: "[]"
string_to_sign does not include /v2, even though /v2 is required as part of the actual url you will request when accessing the api.canonical_querystring and not the whole string_to_sign.= sign and not contain any more characters afterwards. For example, if your HMAC library is adding the characters 3D after the = you must remove these.GET for GET request, POST for POST request, etc...