diff --git a/docs/vision-safe-search.rst b/docs/vision-safe-search.rst
index f86f675c21ba..8f84bc5a9d19 100644
--- a/docs/vision-safe-search.rst
+++ b/docs/vision-safe-search.rst
@@ -4,7 +4,7 @@ Vision Safe Search
 Safe Search Annotation
 ~~~~~~~~~~~~~~~~~~~~~~
 
-.. automodule:: google.cloud.vision.safe
+.. automodule:: google.cloud.vision.safe_search
   :members:
   :undoc-members:
   :show-inheritance:
diff --git a/docs/vision-usage.rst b/docs/vision-usage.rst
index 7e04e9bc8338..c1ca134b8f22 100644
--- a/docs/vision-usage.rst
+++ b/docs/vision-usage.rst
@@ -224,8 +224,7 @@ categorize the entire contents of the image under four categories.
     >>> client = vision.Client()
     >>> with open('./image.jpg', 'rb') as image_file:
     ...     image = client.image(content=image_file.read())
-    >>> safe_search_results = image.detect_safe_search()
-    >>> safe_search = safe_search_results[0]
+    >>> safe_search = image.detect_safe_search()
     >>> safe_search.adult
     <Likelihood.VERY_UNLIKELY: 'VERY_UNLIKELY'>
     >>> safe_search.spoof
diff --git a/system_tests/vision.py b/system_tests/vision.py
index 6f6230bfac8e..43bf28875921 100644
--- a/system_tests/vision.py
+++ b/system_tests/vision.py
@@ -355,7 +355,7 @@ def tearDown(self):
             value.delete()
 
     def _assert_safe_search(self, safe_search):
-        from google.cloud.vision.safe import SafeSearchAnnotation
+        from google.cloud.vision.safe_search import SafeSearchAnnotation
 
         self.assertIsInstance(safe_search, SafeSearchAnnotation)
         self._assert_likelihood(safe_search.adult)
@@ -364,19 +364,13 @@ def _assert_safe_search(self, safe_search):
         self._assert_likelihood(safe_search.violence)
 
     def test_detect_safe_search_content(self):
-        self._pb_not_implemented_skip(
-            'gRPC not implemented for safe search detection.')
         client = Config.CLIENT
         with open(FACE_FILE, 'rb') as image_file:
             image = client.image(content=image_file.read())
-        safe_searches = image.detect_safe_search()
-        self.assertEqual(len(safe_searches), 1)
-        safe_search = safe_searches[0]
+        safe_search = image.detect_safe_search()
         self._assert_safe_search(safe_search)
 
     def test_detect_safe_search_gcs(self):
-        self._pb_not_implemented_skip(
-            'gRPC not implemented for safe search detection.')
         bucket_name = Config.TEST_BUCKET.name
         blob_name = 'faces.jpg'
         blob = Config.TEST_BUCKET.blob(blob_name)
@@ -388,19 +382,13 @@ def test_detect_safe_search_gcs(self):
 
         client = Config.CLIENT
         image = client.image(source_uri=source_uri)
-        safe_searches = image.detect_safe_search()
-        self.assertEqual(len(safe_searches), 1)
-        safe_search = safe_searches[0]
+        safe_search = image.detect_safe_search()
         self._assert_safe_search(safe_search)
 
     def test_detect_safe_search_filename(self):
-        self._pb_not_implemented_skip(
-            'gRPC not implemented for safe search detection.')
         client = Config.CLIENT
         image = client.image(filename=FACE_FILE)
-        safe_searches = image.detect_safe_search()
-        self.assertEqual(len(safe_searches), 1)
-        safe_search = safe_searches[0]
+        safe_search = image.detect_safe_search()
         self._assert_safe_search(safe_search)
 
 
diff --git a/vision/google/cloud/vision/annotations.py b/vision/google/cloud/vision/annotations.py
index e85d1a0c3549..a19527a96c11 100644
--- a/vision/google/cloud/vision/annotations.py
+++ b/vision/google/cloud/vision/annotations.py
@@ -19,7 +19,7 @@
 from google.cloud.vision.color import ImagePropertiesAnnotation
 from google.cloud.vision.entity import EntityAnnotation
 from google.cloud.vision.face import Face
-from google.cloud.vision.safe import SafeSearchAnnotation
+from google.cloud.vision.safe_search import SafeSearchAnnotation
 
 
 _FACE_ANNOTATIONS = 'faceAnnotations'
@@ -61,7 +61,7 @@ class Annotations(object):
 
     :type safe_searches: list
     :param safe_searches:
-        List of :class:`~google.cloud.vision.safe.SafeSearchAnnotation`
+        List of :class:`~google.cloud.vision.safe_search.SafeSearchAnnotation`
 
     :type texts: list
     :param texts: List of
@@ -126,6 +126,8 @@ def _process_image_annotations(image):
         'logos': _make_entity_from_pb(image.logo_annotations),
         'properties': _make_image_properties_from_pb(
             image.image_properties_annotation),
+        'safe_searches': _make_safe_search_from_pb(
+            image.safe_search_annotation),
         'texts': _make_entity_from_pb(image.text_annotations),
     }
 
@@ -170,15 +172,31 @@ def _make_image_properties_from_pb(image_properties):
     return ImagePropertiesAnnotation.from_pb(image_properties)
 
 
+def _make_safe_search_from_pb(safe_search):
+    """Create ``SafeSearchAnnotation`` object from a protobuf response.
+
+    :type safe_search: :class:`~google.cloud.grpc.vision.v1.\
+                            image_annotator_pb2.SafeSearchAnnotation`
+    :param safe_search: Protobuf instance of ``SafeSearchAnnotation``.
+
+    :rtype: :class: `~google.cloud.vision.safe_search.SafeSearchAnnotation`
+    :returns: Instance of ``SafeSearchAnnotation``.
+    """
+    return SafeSearchAnnotation.from_pb(safe_search)
+
+
 def _entity_from_response_type(feature_type, results):
     """Convert a JSON result to an entity type based on the feature.
 
     :rtype: list
     :returns: List containing any of
-              :class:`~google.cloud.vision.color.ImagePropertiesAnnotation`,
               :class:`~google.cloud.vision.entity.EntityAnnotation`,
-              :class:`~google.cloud.vision.face.Face`,
-              :class:`~google.cloud.vision.safe.SafeSearchAnnotation`.
+              :class:`~google.cloud.vision.face.Face`
+
+              or one of
+
+              :class:`~google.cloud.vision.safe_search.SafeSearchAnnotation`,
+              :class:`~google.cloud.vision.color.ImagePropertiesAnnotation`.
     """
     detected_objects = []
     if feature_type == _FACE_ANNOTATIONS:
@@ -187,7 +205,7 @@ def _entity_from_response_type(feature_type, results):
     elif feature_type == _IMAGE_PROPERTIES_ANNOTATION:
         return ImagePropertiesAnnotation.from_api_repr(results)
     elif feature_type == _SAFE_SEARCH_ANNOTATION:
-        detected_objects.append(SafeSearchAnnotation.from_api_repr(results))
+        return SafeSearchAnnotation.from_api_repr(results)
     else:
         for result in results:
             detected_objects.append(EntityAnnotation.from_api_repr(result))
diff --git a/vision/google/cloud/vision/face.py b/vision/google/cloud/vision/face.py
index 0809ba16b7dd..354ff08d59ce 100644
--- a/vision/google/cloud/vision/face.py
+++ b/vision/google/cloud/vision/face.py
@@ -17,26 +17,12 @@
 
 from enum import Enum
 
-from google.cloud.grpc.vision.v1 import image_annotator_pb2
-
 from google.cloud.vision.geometry import BoundsBase
+from google.cloud.vision.likelihood import _get_pb_likelihood
 from google.cloud.vision.likelihood import Likelihood
 from google.cloud.vision.geometry import Position
 
 
-def _get_pb_likelihood(likelihood):
-    """Convert protobuf Likelihood integer value to Likelihood instance.
-
-    :type likelihood: int
-    :param likelihood: Protobuf integer representing ``Likelihood``.
-
-    :rtype: :class:`~google.cloud.vision.likelihood.Likelihood`
-    :returns: Instance of ``Likelihood`` converted from protobuf value.
-    """
-    likelihood_pb = image_annotator_pb2.Likelihood.Name(likelihood)
-    return Likelihood[likelihood_pb]
-
-
 class Angles(object):
     """Angles representing the positions of a face."""
     def __init__(self, roll, pan, tilt):
diff --git a/vision/google/cloud/vision/likelihood.py b/vision/google/cloud/vision/likelihood.py
index 93c9ddacd81d..fd249e41dff1 100644
--- a/vision/google/cloud/vision/likelihood.py
+++ b/vision/google/cloud/vision/likelihood.py
@@ -17,6 +17,21 @@
 
 from enum import Enum
 
+from google.cloud.grpc.vision.v1 import image_annotator_pb2
+
+
+def _get_pb_likelihood(likelihood):
+    """Convert protobuf Likelihood integer value to Likelihood enum.
+
+    :type likelihood: int
+    :param likelihood: Protobuf integer representing ``Likelihood``.
+
+    :rtype: :class:`~google.cloud.vision.likelihood.Likelihood`
+    :returns: Enum ``Likelihood`` converted from protobuf value.
+    """
+    likelihood_pb = image_annotator_pb2.Likelihood.Name(likelihood)
+    return Likelihood[likelihood_pb]
+
 
 class Likelihood(Enum):
     """A representation of likelihood to give stable results across upgrades.
diff --git a/vision/google/cloud/vision/safe.py b/vision/google/cloud/vision/safe_search.py
similarity index 76%
rename from vision/google/cloud/vision/safe.py
rename to vision/google/cloud/vision/safe_search.py
index 9483da6d1aa1..9b531837db8c 100644
--- a/vision/google/cloud/vision/safe.py
+++ b/vision/google/cloud/vision/safe_search.py
@@ -1,4 +1,4 @@
-# Copyright 2016 Google Inc.
+# Copyright 2017 Google Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
 
 """Safe search class for information returned from annotating an image."""
 
-
+from google.cloud.vision.likelihood import _get_pb_likelihood
 from google.cloud.vision.likelihood import Likelihood
 
 
@@ -51,17 +51,32 @@ def from_api_repr(cls, response):
         :param response: Dictionary response from Vision API with safe search
                          data.
 
-        :rtype: :class:`~google.cloud.vision.safe.SafeSearchAnnotation`
+        :rtype: :class:`~google.cloud.vision.safe_search.SafeSearchAnnotation`
         :returns: Instance of ``SafeSearchAnnotation``.
         """
-        adult_likelihood = getattr(Likelihood, response['adult'])
-        spoof_likelihood = getattr(Likelihood, response['spoof'])
-        medical_likelihood = getattr(Likelihood, response['medical'])
-        violence_likelihood = getattr(Likelihood, response['violence'])
+        adult_likelihood = Likelihood[response['adult']]
+        spoof_likelihood = Likelihood[response['spoof']]
+        medical_likelihood = Likelihood[response['medical']]
+        violence_likelihood = Likelihood[response['violence']]
 
         return cls(adult_likelihood, spoof_likelihood, medical_likelihood,
                    violence_likelihood)
 
+    @classmethod
+    def from_pb(cls, image):
+        """Factory: construct SafeSearchAnnotation from Vision API response.
+
+        :type image: :class:`~google.cloud.grpc.vision.v1.image_annotator_pb2.\
+                      SafeSearchAnnotation`
+        :param image: Protobuf response from Vision API with safe search data.
+
+        :rtype: :class:`~google.cloud.vision.safe_search.SafeSearchAnnotation`
+        :returns: Instance of ``SafeSearchAnnotation``.
+        """
+        values = [image.adult, image.spoof, image.medical, image.violence]
+        classifications = map(_get_pb_likelihood, values)
+        return cls(*classifications)
+
     @property
     def adult(self):
         """Represents the adult contents likelihood for the image.
diff --git a/vision/unit_tests/test_annotations.py b/vision/unit_tests/test_annotations.py
index d98ecb376196..68a270e3122d 100644
--- a/vision/unit_tests/test_annotations.py
+++ b/vision/unit_tests/test_annotations.py
@@ -67,6 +67,8 @@ def test_ctor(self):
         self.assertEqual(annotations.texts, [True])
 
     def test_from_pb(self):
+        from google.cloud.vision.likelihood import Likelihood
+        from google.cloud.vision.safe_search import SafeSearchAnnotation
         from google.cloud.grpc.vision.v1 import image_annotator_pb2
 
         image_response = image_annotator_pb2.AnnotateImageResponse()
@@ -76,9 +78,16 @@ def test_from_pb(self):
         self.assertEqual(annotations.faces, [])
         self.assertEqual(annotations.landmarks, [])
         self.assertEqual(annotations.texts, [])
-        self.assertEqual(annotations.safe_searches, ())
         self.assertIsNone(annotations.properties)
 
+        self.assertIsInstance(annotations.safe_searches, SafeSearchAnnotation)
+        safe_search = annotations.safe_searches
+        unknown = Likelihood.UNKNOWN
+        self.assertIs(safe_search.adult, unknown)
+        self.assertIs(safe_search.spoof, unknown)
+        self.assertIs(safe_search.medical, unknown)
+        self.assertIs(safe_search.violence, unknown)
+
 
 class Test__make_entity_from_pb(unittest.TestCase):
     def _call_fut(self, annotations):
diff --git a/vision/unit_tests/test_client.py b/vision/unit_tests/test_client.py
index 0941390a0f61..f3f972c1f6cc 100644
--- a/vision/unit_tests/test_client.py
+++ b/vision/unit_tests/test_client.py
@@ -410,7 +410,7 @@ def test_text_detection_from_source(self):
 
     def test_safe_search_detection_from_source(self):
         from google.cloud.vision.likelihood import Likelihood
-        from google.cloud.vision.safe import SafeSearchAnnotation
+        from google.cloud.vision.safe_search import SafeSearchAnnotation
         from unit_tests._fixtures import SAFE_SEARCH_DETECTION_RESPONSE
 
         RETURNED = SAFE_SEARCH_DETECTION_RESPONSE
@@ -420,15 +420,16 @@ def test_safe_search_detection_from_source(self):
         client._connection = _Connection(RETURNED)
 
         image = client.image(source_uri=IMAGE_SOURCE)
-        safe_search = image.detect_safe_search()[0]
+        safe_search = image.detect_safe_search()
         self.assertIsInstance(safe_search, SafeSearchAnnotation)
         image_request = client._connection._requested[0]['data']['requests'][0]
         self.assertEqual(IMAGE_SOURCE,
                          image_request['image']['source']['gcs_image_uri'])
-        self.assertEqual(safe_search.adult, Likelihood.VERY_UNLIKELY)
-        self.assertEqual(safe_search.spoof, Likelihood.UNLIKELY)
-        self.assertEqual(safe_search.medical, Likelihood.POSSIBLE)
-        self.assertEqual(safe_search.violence, Likelihood.VERY_UNLIKELY)
+
+        self.assertIs(safe_search.adult, Likelihood.VERY_UNLIKELY)
+        self.assertIs(safe_search.spoof, Likelihood.UNLIKELY)
+        self.assertIs(safe_search.medical, Likelihood.POSSIBLE)
+        self.assertIs(safe_search.violence, Likelihood.VERY_UNLIKELY)
 
     def test_safe_search_no_results(self):
         RETURNED = {
diff --git a/vision/unit_tests/test_safe_search.py b/vision/unit_tests/test_safe_search.py
new file mode 100644
index 000000000000..5bc06ac47c52
--- /dev/null
+++ b/vision/unit_tests/test_safe_search.py
@@ -0,0 +1,70 @@
+# Copyright 2016 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+
+
+class TestSafeSearchAnnotation(unittest.TestCase):
+    @staticmethod
+    def _get_target_class():
+        from google.cloud.vision.safe_search import SafeSearchAnnotation
+        return SafeSearchAnnotation
+
+    def test_safe_search_annotation(self):
+        from google.cloud.vision.likelihood import Likelihood
+        from unit_tests._fixtures import SAFE_SEARCH_DETECTION_RESPONSE
+
+        response = SAFE_SEARCH_DETECTION_RESPONSE['responses'][0]
+        safe_search_response = response['safeSearchAnnotation']
+
+        safe_search = self._get_target_class().from_api_repr(
+            safe_search_response)
+
+        self.assertIs(safe_search.adult, Likelihood.VERY_UNLIKELY)
+        self.assertIs(safe_search.spoof, Likelihood.UNLIKELY)
+        self.assertIs(safe_search.medical, Likelihood.POSSIBLE)
+        self.assertIs(safe_search.violence, Likelihood.VERY_UNLIKELY)
+
+    def test_pb_safe_search_annotation(self):
+        from google.cloud.vision.likelihood import Likelihood
+        from google.cloud.grpc.vision.v1.image_annotator_pb2 import (
+            Likelihood as LikelihoodPB)
+        from google.cloud.grpc.vision.v1 import image_annotator_pb2
+
+        possible = LikelihoodPB.Value('POSSIBLE')
+        possible_name = Likelihood.POSSIBLE
+        safe_search_annotation = image_annotator_pb2.SafeSearchAnnotation(
+            adult=possible, spoof=possible, medical=possible, violence=possible
+        )
+
+        safe_search = self._get_target_class().from_pb(safe_search_annotation)
+
+        self.assertIs(safe_search.adult, possible_name)
+        self.assertIs(safe_search.spoof, possible_name)
+        self.assertIs(safe_search.medical, possible_name)
+        self.assertIs(safe_search.violence, possible_name)
+
+    def test_empty_pb_safe_search_annotation(self):
+        from google.cloud.vision.likelihood import Likelihood
+        from google.cloud.grpc.vision.v1 import image_annotator_pb2
+
+        unknown = Likelihood.UNKNOWN
+        safe_search_annotation = image_annotator_pb2.SafeSearchAnnotation()
+
+        safe_search = self._get_target_class().from_pb(safe_search_annotation)
+
+        self.assertIs(safe_search.adult, unknown)
+        self.assertIs(safe_search.spoof, unknown)
+        self.assertIs(safe_search.medical, unknown)
+        self.assertIs(safe_search.violence, unknown)