B

Documentation & tutorials

Integrate Panda with your web, mobile or desktop app.

HTTP API

This document outlines the V2 of the HTTP interface that Panda exposes to other applications.

Base URL

We provide two API end-points, one for US and one for EU.

Videos

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.json

Returns a collection of Video objects.

Optional parameters

status: one of 'success', 'fail', 'processing'. Filter by status
          page: default 1
          per_page: default 100
          

Example response

[{
            "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.json

Parameters

id: ID of the Video object
          

Example request

GET /videos/d891d9a45c698d587831466f236c6c6c.json

Example response

{
            "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.json

Required Parameters

id: ID of the Video object
          

Required Parameters

status: one of 'success', 'fail', 'processing'. Filter by status profile_id: filter by profile_id profile_name: filter by profile_name

Example request

GET /videos/d891d9a45c698d587831466f236c6c6c/encodings.json

Example response

[{
            "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.json

Just after the media is uploaded, we analyze and store the metadata that we can extract from it.

Example request

GET /videos/d891d9a45c698d587831466f236c6c6c/metadata.json

Example response

{
            "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.json

If 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.

Required Parameters

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.

Optional Parameters

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.
          
Path Format

The following keywords can be combined to create a path format.

:id         : Id of 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"

Path format should be unique for each encoding, so :id is required.

Payload

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.

Example request

POST /videos.json
          
          profiles: "h264,webm"
          path_format: "my-path/:id"
          payload: "2456"
          

Example response

{
            "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.json

Before 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.

Example request

POST /videos/upload.json
          
          file_size: 805301
          file_name: panda.mp4
          

Example response

location: http://vm-37gup6.upload.pandastream.com/upload/session?id=abcdefgh
          id: abcdefgh
          
          status: 201
          

Required parameters

file_size: Size in bytes of the video
          file_name: File name of the video
          

Optional parameters

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.json

Example request

DELETE /videos/d891d9a45c698d587831466f236c6c6c.json
          

Example response

status: 200
          

Encodings

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.json

Optional parameters

status: 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
          

Example request

GET /encoding.json?status=success
          

Example response

[{
            "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.json

Example request

GET /encodings/2f8760b7e0d4c7dbe609b5872be9bc3b.json
          

Example response

{
            "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.json

Create a new encoding for a video that already exists.

Required parameters

video_id: ID of existing video
          profile_id: ID of existing profile
          profile_name: Name of existing profile
          

Example request

POST /encodings.json
          
          video_id: d891d9a45c698d587831466f236c6c6c
          profile_name: h264
          

Example response

{
            "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.json

Cancel an encoding

Example request

POST /encodings/2f8760b7e0d4c7dbe609b5872be9bc3b/cancel.json
          

Example response

status: 200
          

POST /encodings/:id/retry.json

Retry a failed encoding

Example request

POST /encodings/2f8760b7e0d4c7dbe609b5872be9bc3b/retry.json
          

Example response

status: 200
          

DELETE /encodings/:id.json

Example request

DELETE /encodings/2f8760b7e0d4c7dbe609b5872be9bc3b.json
          

Example response

status: 200
          

Profiles

GET /profiles.json

Example request

GET /profiles.json
          

Example response

[{
             "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.json

Example request

GET /profiles/40d9f8711d64aaa74f88462e9274f39a.json
          

Example response

{
            "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.json

Optional parameters

name:                 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)
          
Using presets

Required parameters

preset_name
          

Example request

POST /profiles.json
          
          preset_name: 'h264'
          

Example response

{
            "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"
          }
          
Using custom settings

Required parameters

command: line break separated list of encoding commands to run. 
          extname: file extension (example: '.mp4')
          

Example request

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$
          

Example response

{
            "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.json

Optional parameters

Same as POST /profiles.json but preset_name

Example request

PUT /profiles/40d9f8711d64aaa74f88462e9274f39a.json
          
          title: The best custom profile
          

Example response

{
            "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.json

Example request

DELETE /profiles/40d9f8711d64aaa74f88462e9274f39a.json
          

Example response

status: 200
          

Clouds

GET /clouds/:id.json

Example request

GET /clouds/e122090f4e506ae9ee266c3eb78a8b67.json
          

Example response

{
            "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.json

Optional parameters

name
          s3_videos_bucket
          aws_access_key
          aws_secret_key
          priority (value between 2 and 200, default is 99)
          

Example request

PUT /clouds/e122090f4e506ae9ee266c3eb78a8b67.json
          
          name: "my_first_cloud"
          s3_videos_bucket: "my_own_bucket"
          aws_access_key: XQwEwFR
          aws_secret_key: XoSV2fV
          priority: 150
          

Example response

{
            "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"
          }
          

Notifications

GET /notifications.json

Example request

GET /notifications.json
          

Example response

{
            "url": "null",
            "events": {
              "video_created": false,
              "video_encoded": false,
              "encoding_progress": false,
              "encoding_completed": false
            }
          }
          

PUT /notifications.json

Example request

PUT /notifications.json
          
          url: "http://example.com/panda_notification"
          events['video_encoded'] = true
          

Example response

{
            "url": "http://example.com/panda_notification", 
            "events": {
              "video_created": false,
              "video_encoded": true,
              "encoding_progress": false,
              "encoding_completed": false
            }
          }
          

Error classes and Error messages

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.

API Errors

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

500 (InternalError)

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

401 NotAuthorized

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.

Example response

{"error" : "NotAuthorized", "message" : "Signatures do not match"}
          
          {"error" : "NotAuthorized", "message" : "Signatures expired"}
          

404 RecordNotFound

A record with the id supplied could not be found.

Example response (example)

{"error" : "RecordNotFound", "message" : "Couldn't find Video with ID=X"}
          

400 BadRequest

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.

Example response (2 examples)

{"error" : "BadRequest", "message" : "Currently only .json is supported as a format"}
          
          {"error" : "BadRequest", "message" : "All required parameters were not supplied: access_key, signature, timestamp"}
          

413 FileSizeLimitExceeded

If you attempt to upload a file > 10MB with a sandbox account, the following error will be returned.

Example response

{"error" : "BadRequest", "message" : "File size limit for this account is set to 10485760 bytes"}
          

API Authentication

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
          

Build the signature

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&timestamp=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

Worked example

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&timestamp=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&timestamp=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&timestamp=2011-03-01T15:39:10.260762Z&signature=kVnZs%2FNX13ldKPdhFYoVnoclr8075DwiZF0TGgIbMsc%3D
          status: "200"
          body: "[]"
          

Things to look into if you're having signature issues

  • Ensure that the timestamp is uppercase and strict iso8601 format.
  • Check the url in the 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.
  • Only url-encode the parameter values of the canonical_querystring and not the whole string_to_sign.
  • The signature should end in an = 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.
  • Make sure you use the binary digest of the HMAC and not any other outputs such as the hex digest.
  • Make sure that URL encoded characters are uppercase (%3A and not %3a).
  • Make sure inside the string_to_sign you use GET for GET request, POST for POST request, etc...