WebPurify Image Moderation Add-on

Overview

Cloudinary is a cloud-based service that provides an end-to-end image management solution including uploads, storage, manipulations, optimizations and delivery.

Cloudinary offers a very rich set of image uploading, manipulation and media library management capabilities. Cloudinary allows you to upload images to the cloud, manipulate them on-the-fly and deliver them to your users optimized and cached via a fast CDN.

WebPurify offers an image moderation service based on human moderator experts. Cloudinary provides an add-on for using WebPurify's image moderation capabilities, fully integrated into Cloudinary's image management and manipulation pipeline.

With WebPurify's image moderation add-on, you can extend Cloudinary's powerful cloud-based image media library and delivery capabilities with automatically moderation of your photos. When using the WebPurify add-on, user uploaded images are automatically moderated, making sure that no adult or offensive photos are displayed to your web and mobile viewers.

Note: The criteria for image moderation can be customized by contacting Cloudinary support.

Automatic image moderation flow

The following list describes the flow of uploading and displaying moderated images using Cloudinary and the WebPurify image moderation add-on:

  • Image upload
    • Your users upload an image to Cloudinary through your application.
    • The uploaded images are set to a 'pending' status, with short term CDN caching.
    • You can decide whether to display the pending image or wait for its approval.
  • Image moderation
    • The uploaded image is sent to WebPurify for asynchronous moderation in the background.
    • The image is either approved or rejected by WebPurify's moderation add-on.
    • An optional notification callback is sent to your application with the image moderation result.
    • If the image is approved, its cache settings are modified to be long-term.
    • A rejected image is removed and the original rejected image is moved to a secondary backup repository, consuming additional storage.
  • Manual override
    • Pending, approved and rejected images can be listed programatically using Cloudinary's API or interactively using our online Media Library Web interface.
    • You can manually override the automatic moderation using the API or Media Library.

Request image moderation

Before you start, if you haven't done so already, please sign-up to a free Cloudinary account. After signing up, you can try the WebPurify image moderation add-on for free and later on subscribe to an WebPurify image moderation add-on plan that best matches your usage requirements.

Note: WebPurify automatically rejects the following images: URLs with no image format extension, images with the image format written in CAPS and any images in the PDF format.

To simplify the add-on integration, you can use Cloudinary's client libraries for: Ruby on Rails, Python & Django, PHP, Node.js, jQuery, AngularJS, Java, .NET, iOS, Android and Scala.

In order to request moderation of uploaded images, simply set the 'moderation' upload API parameter to 'webpurify':

Ruby:
Cloudinary::Uploader.upload("local_file.jpg", 
  :moderation => "webpurify")
PHP:
\Cloudinary\Uploader::upload("local_file.jpg", 
  array("moderation" => "webpurify"));
Python:
cloudinary.uploader.upload("local_file.jpg",
  moderation = "webpurify")
Node.js:
cloudinary.uploader.upload("local_file.jpg", 
  function(result) { console.log(result); }, 
  { moderation: "webpurify" });
Java:
cloudinary.uploader().upload("local_file.jpg", 
  ObjectUtils.asMap("moderation", "webpurify"));

The uploaded image is available for delivery based on the randomly assigned public ID with short-term caching of 10 minutes. Image moderation by the WebPurify add-on is performed asynchronously and should be completed within up to a few minutes.

The following snippet shows the response of the upload API call that signifies that the WebPurify moderation is in the 'pending' status.

Ruby:
{
  "public_id" => "iswyvngmi8avzvomdpfc",
  "version" => 1393751993,
  "url" =>
    "http://res.cloudinary.com/demo/image/upload/v1393751993/iswyvngmi8avzvomdpfc.jpg",
  "moderation" => [{"status"=>"pending", "kind"=>"webpurify"}],
  ...        
}
PHP:
array(14) {
  ["public_id"]=>
  string(20) "iswyvngmi8avzvomdpfc"
  ["version"]=>
  int(1393751993)
  ["url"]=>
  string(80) "http://res.cloudinary.com/demo/image/upload/v1393751993/iswyvngmi8avzvomdpfc.jpg"
  ["moderation"]=>
  array(1) {
    [0]=>
    array(2) {
      ["status"]=>
      string(7) "pending"
      ["kind"]=>
      string(9) "webpurify"
    }
  }
  ...
}
Python:
{ 
  u'public_id': u'iswyvngmi8avzvomdpfc',
  u'version': 1393751993,
  u'url': 
    u'http://res.cloudinary.com/demo/image/upload/v1393751993/iswyvngmi8avzvomdpfc.jpg',
  u'moderation': [{ u'kind': u'webpurify', u'status': u'pending'}],
  ...
}
Node.js:
{ 
  public_id: 'iswyvngmi8avzvomdpfc',
  version: 1393751993,
  url: 
    'http://res.cloudinary.com/demo/image/upload/v1393751993/iswyvngmi8avzvomdpfc.jpg',
  moderation': [{ 'kind': 'webpurify', 'status': 'pending'}],
  ...
}
Java:
{
  "public_id": "iswyvngmi8avzvomdpfc",
  "version": 1393751993,
  "url": "http:\/\/res.cloudinary.com\/demo\/image\/upload\/v1393751993\/iswyvngmi8avzvomdpfc.jpg",
  "moderation": [{ "status": "pending", "kind": "webpurify" }],
  ...
}

Status notification

Due to the fact that the WebPurify add-on moderates images asynchronously, you might want to get notified when the moderation process is completed.

When calling the upload API with WebPurify image moderation, you can set the 'notification_url' to a public HTTP or HTTPS URL of your online web application. Cloudinary sends a POST request to the specified endpoint when WebPurify moderation is completed.

Ruby:
Cloudinary::Uploader.upload("local_file.jpg", 
  :moderation => "webpurify",
  :notification_url => "http://mysite.example.com/hooks")
PHP:
\Cloudinary\Uploader::upload("local_file.jpg", 
  array(
    "moderation" => "webpurify",
    "notification_url" => "http://mysite.example.com/hooks"));
Python:
cloudinary.uploader.upload("local_file.jpg",
  moderation = "webpurify",
  notification_url = "http://mysite.example.com/hooks")
Node.js:
cloudinary.uploader.upload("local_file.jpg", 
  function(result) { console.log(result); }, 
  { 
    moderation: "webpurify",
    notification_url: "http://mysite.example.com/hooks" 
  });
Java:
cloudinary.uploader().upload("local_file.jpg", 
  ObjectUtils.asMap("moderation", "webpurify",
                   "notification_url", "http://mysite.example.com/hooks"));

The following JSON snippet is an example of a POST request sent to the notification URL when moderation is completed. The 'moderation_status' value in this case can be either 'approved' or 'rejected'

{
  "moderation_response": "approved",
  "moderation_status": "approved",
  "moderation_kind": "webpurify",
  "moderation_updated_at": "2014-03-02T09:33:43Z",
  "public_id": "l8yvniicjhmyxuuqqyqy",
  "uploaded_at": "2014-03-02T09:33:42Z",
  "version": 1393752822,
  "url": 
    "http://res.cloudinary.com/demo/image/upload/v1393752822/l8yvniicjhmyxuuqqyqy.jpg",
  "secure_url": 
    "https://res.cloudinary.com/demo/image/upload/v1393752822/l8yvniicjhmyxuuqqyqy.jpg",
  "etag": "83340520d28b704ca4f4b019effb33dc",
  "notification_type": "moderation" 
}
{
  "moderation_response": "rejected",
  "moderation_status": "rejected",
  "moderation_kind": "webpurify",
  "moderation_updated_at": "2014-03-02T20:47:48Z",
  "public_id": "nuzn4riqxhhzfyfljjxv",
  "uploaded_at": "2014-03-02T20:47:47Z",
  "version": 1393793267,
  "url":
    "http://res.cloudinary.com/demo/image/upload/v1393793267/nuzn4riqxhhzfyfljjxv.jpg",
  "secure_url":
    "https://res.cloudinary.com/demo/image/upload/v1393793267/nuzn4riqxhhzfyfljjxv.jpg",
  "etag":"06778590d96907b60b5fa83795e7df3b",
  "notification_type":"moderation"
}

The JSON content is signed using the API Secret of your Cloudinary account. For more details regarding Cloudinary's notifications and webhooks, see this blog post.

Query status

As an alternative to receiving a webhook when image moderation is completed, you can poll Cloudinary's Admin API for checking the image moderation status of previously uploaded image:

Ruby:
Cloudinary::Api.resource('l8yvniicjhmyxuuqqyqy')
PHP:
$api = new \Cloudinary\Api();
$api->resource("l8yvniicjhmyxuuqqyqy");
Python:
cloudinary.api.resource("l8yvniicjhmyxuuqqyqy")
Node.js:
cloudinary.api.resource('l8yvniicjhmyxuuqqyqy', 
  function(result)  { console.log(result) });
Java:
cloudinary.api().resource("l8yvniicjhmyxuuqqyqy", 
  ObjectUtils.emptyMap());

The following JSON snippet is the response of the Admin API resource details of an image being moderated. As you can see, the moderation status is set to 'approved'.

{ 
  "public_id": "l8yvniicjhmyxuuqqyqy", 
  "format": "jpg", 
  "version": 1393752822, 
  "resource_type": "image", 
  "type": "upload", 
  "created_at": "2014-03-02T09:33:42Z", 
  "bytes": 120253, 
  "width": 864, 
  "height": 576, 
  "backup": true, 
  "url":
    "http://res.cloudinary.com/demo/image/upload/v1393752822/l8yvniicjhmyxuuqqyqy.jpg", 
  "secure_url": 
    "https://res.cloudinary.com/demo/image/upload/v1393752822/l8yvniicjhmyxuuqqyqy.jpg", 
  "moderation": 
    [{ 
       "response": "approved", 
       "status": "approved", 
       "kind": "webpurify", 
       "updated_at": "2014-03-02T09:33:43Z"
     }], 
   "next_cursor": "699718787ae2aa6423bd1946864e8768", 
   "derived": []
 }

Image moderation queue

Cloudinary's Admin API can be used to list the queue of all moderated images. You can list all three queues of pending, approved and rejected images by specifying the second parameter of the 'resources_by_moderation' API method.

Ruby:
Cloudinary::Api.resources_by_moderation("webpurify", "pending")
PHP:
$api = new \Cloudinary\Api();
$api->resources_by_moderation("webpurify", "pending");
Python:
cloudinary.api.resources_by_moderation("webpurify", "pending")
Node.js:
cloudinary.api.resources_by_moderation('webpurify', 'pending', 
  function(result)  { console.log(result) });
Java:
cloudinary.api().resourcesByModeration("webpurify", "pending", 
  ObjectUtils.emptyMap());
{
 "resources"=>
  [{
    "public_id": "q7vcvrfjm9mj4bfp3qc8",
    "format": "jpg",
    "version": 1393794403,
    "resource_type": "image",
    "type": "upload",
    "created_at": "2014-03-02T21:06:43Z",
    "bytes": 120253,
    "width": 864,
    "height": 576,
    "backup": true,
    "url": 
     "http://res.cloudinary.com/demo/image/upload/v1393794403/q7vcvrfjm9mj4bfp3qc8.jpg",
    "secure_url": 
     "https://res.cloudinary.com/demo/image/upload/v1393794403/q7vcvrfjm9mj4bfp3qc8.jpg"
   },
   {
    "public_id": "zp4fgdbabhlwwa7bxu84",
    "format": "jpg",
    "version": 1393794399,
    "resource_type": "image",
    "type": "upload",
    "created_at": "2014-03-02T21:06:39Z",
    "bytes": 120253,
    "width": 864,
    "height": 576,
    "backup": true,
    "url": 
     "http://res.cloudinary.com/demo/image/upload/v1393794399/zp4fgdbabhlwwa7bxu84.jpg",
    "secure_url": 
     "https://res.cloudinary.com/demo/image/upload/v1393794399/zp4fgdbabhlwwa7bxu84.jpg"
   }
 ]
}

Listing the queue of images rejected by WebPurify image moderation:

Ruby:
Cloudinary::Api.resources_by_moderation("webpurify", "rejected")
PHP:
$api = new \Cloudinary\Api();
$api->resources_by_moderation("webpurify", "rejected");
Python:
cloudinary.api.resources_by_moderation("webpurify", "rejected")
Node.js:
cloudinary.api.resources_by_moderation('webpurify', 'rejected', 
  function(result)  { console.log(result) });
Java:
cloudinary.api().resourcesByModeration("webpurify", "rejected", 
  ObjectUtils.emptyMap());

Listing the queue of images approved by WebPurify image moderation:

Ruby:
Cloudinary::Api.resources_by_moderation("webpurify", "approved")
PHP:
$api = new \Cloudinary\Api();
$api->resources_by_moderation("webpurify", "approved");
Python:
cloudinary.api.resources_by_moderation("webpurify", "approved")
Node.js:
cloudinary.api.resources_by_moderation('webpurify', 'approved', 
  function(result)  { console.log(result) });
Java:
cloudinary.api().resourcesByModeration("webpurify", "approved", 
  ObjectUtils.emptyMap());

Manual override

While the automatic image moderation of the WebPurify add-on is very accurate, in some cases you may want to manually override the moderation decision. You can either approve a previously rejected image or reject an approved one.

One way to manually override the moderation result is using Cloudinary's Media Library Web interface.

As you can see in the following screenshot, you can click on the 'Moderation Queue' link, select 'WebPurify' and then the specific queue of images. In this case, we list the queue of images rejected by WebPurify. You can then click on the green Approve button to revert the decision and recover the original rejected image.

Webpurify_rejected_moderation_queue

As the following screenshot illustrates, the same goes for the list of approved images. You can click on the red Reject button to revert the decision and prevent a certain image from being publicly available to your users.

Webpurify_approved_moderation_queue

Alternatively to using the Web interface, you can use Cloudinary's Admin API to manually override the moderation result. The following sample code uses the 'update' API method while specifying a public ID of a moderated image and setting the 'moderation_status' parameter to either the 'approved' or the 'rejected' status.

Ruby:
Cloudinary::Api.update("hwepb67oxzh4lrigssld", 
  :moderation_status => "approved")
PHP:
$api = new \Cloudinary\Api();
$api->update("hwepb67oxzh4lrigssld", 
  array("moderation_status" => "approved"));
Python:
cloudinary.api.update("hwepb67oxzh4lrigssld",
  moderation_status = "approved")
Node.js:
cloudinary.api.update("hwepb67oxzh4lrigssld", 
  function(result) { console.log(result); }, 
  { moderation_status: "approved" });
Java:
cloudinary.api().update("hwepb67oxzh4lrigssld", 
  ObjectUtils.asMap("moderation_status", "approved"));
Ruby:
Cloudinary::Api.update("hwepb67oxzh4lrigssld", 
  :moderation_status => "rejected")
PHP:
$api = new \Cloudinary\Api();
$api->update("hwepb67oxzh4lrigssld", 
  array("moderation_status" => "rejected"));
Python:
cloudinary.api.update("hwepb67oxzh4lrigssld",
  moderation_status = "rejected")
Node.js:
cloudinary.api.update("hwepb67oxzh4lrigssld", 
  function(result) { console.log(result); }, 
  { moderation_status: "rejected" });
Java:
cloudinary.api().update("hwepb67oxzh4lrigssld", 
  ObjectUtils.asMap("moderation_status", "rejected"));