diff --git a/src/metabase/api/common.clj b/src/metabase/api/common.clj
index 1bcca2b55f397bf2ba632a09cd8dbf44bee10e2b..cd30dc149f15aeff8021864e98676fa595d81efd 100644
--- a/src/metabase/api/common.clj
+++ b/src/metabase/api/common.clj
@@ -123,8 +123,7 @@
     -> (check (contains? #{:fav :all :mine} f)
          [400 (str \"Invalid value '\" f \"' for 'f': must be one of: #{:fav :all :mine}\")])"
   [valid-values-set symb value]
-  {:pre [(set? valid-values-set)
-         (symbol? symb)]}
+  {:pre [(set? valid-values-set) (symbol? symb)]}
   (checkp-with (partial contains? valid-values-set) symb value (str "must be one of: " valid-values-set)))
 
 
@@ -362,7 +361,7 @@
 (defannotation NonEmptyString
   "Param must be a non-empty string (strings that only contain whitespace are considered empty)."
   [symb value :nillable]
-  (checkp-with (complement clojure.string/blank?) symb value "value must be a non-empty string."))
+  (checkp-with (complement s/blank?) symb value "value must be a non-empty string."))
 
 (defannotation PublicPerms
   "Param must be an integer and either `0` (no public permissions), `1` (public may read), or `2` (public may read and write)."
diff --git a/src/metabase/api/field.clj b/src/metabase/api/field.clj
index 87a9287e21e4361228dfeedea59cda0051161d72..3d0adcf2c53e62c861733a62913e060e041d783b 100644
--- a/src/metabase/api/field.clj
+++ b/src/metabase/api/field.clj
@@ -17,10 +17,6 @@
   [symb value :nillable]
   (checkp-contains? field/visibility-types symb (keyword value)))
 
-(defannotation FieldType
-  "Param must be a valid `Field` base type."
-  [symb value :nillable]
-  (checkp-contains? field/field-types symb (keyword value)))
 
 (defendpoint GET "/:id"
   "Get `Field` with ID."
@@ -29,35 +25,46 @@
          read-check
          (hydrate [:table :db])))
 
+
 (defendpoint PUT "/:id"
   "Update `Field` with ID."
-  [id :as {{:keys [special_type visibility_type fk_target_field_id description display_name caveats points_of_interest], :as body} :body}]
-  {special_type    FieldSpecialType
-   visibility_type FieldVisibilityType
-   display_name    NonEmptyString}
+  [id :as {{:keys [caveats description display_name fk_target_field_id points_of_interest special_type visibility_type], :as body} :body}]
+  {caveats            NonEmptyString
+   description        NonEmptyString
+   display_name       NonEmptyString
+   fk_target_field_id Integer
+   points_of_interest NonEmptyString
+   special_type       FieldSpecialType
+   visibility_type    FieldVisibilityType}
+
   (let-404 [field (Field id)]
     (write-check field)
-    (let [special_type       (if (contains? body :special_type) special_type (:special_type field))
+    (println "special_type:" special_type) ; NOCOMMIT
+    (let [special_type       (keyword (get body :special_type (:special_type field)))
           visibility_type    (or visibility_type (:visibility_type field))
-          fk_target_field_id (when (= :fk special_type)
-                               ;; only let target field be set for :fk type fields,
-                               ;; and if it's not in the payload then leave the current value
-                               (if (contains? body :fk_target_field_id)
-                                 fk_target_field_id
-                                 (:fk_target_field_id field)))]
-      (check-400 (field/valid-metadata? (:base_type field) (:field_type field) special_type visibility_type))
-      ;; validate that fk_target_field_id is a valid Field within the same database as our field
-      (when fk_target_field_id
-        (checkp (db/exists? Field :id fk_target_field_id)
+          ;; only let target field be set for :fk type fields, and if it's not in the payload then leave the current value
+          fk-target-field-id (when (= :fk special_type)
+                               (get body :fk_target_field_id (:fk_target_field_id field)))]
+      ;; make sure that the special type is allowed for the base type
+      (check (field/valid-special-type-for-base-type? special_type (:base_type field))
+        [400 (format "Special type %s cannot be used for fields with base type %s. Base type must be one of: %s"
+                     special_type
+                     (:base_type field)
+                     (field/special-type->valid-base-types special_type))])
+      ;; validate that fk_target_field_id is a valid Field
+      ;; TODO - we should also check that the Field is within the same database as our field
+      (when fk-target-field-id
+        (checkp (db/exists? Field :id fk-target-field-id)
           :fk_target_field_id "Invalid target field"))
-      ;; update the Field.  start with keys that may be set to NULL then conditionally add other keys if they have values
-      (check-500 (db/update! Field id (merge {:description        description
-                                              :caveats            caveats
+      ;; everything checks out, now update the field
+      (check-500 (db/update! Field id (merge {:caveats            caveats
+                                              :description        description
+                                              :fk_target_field_id fk_target_field_id
                                               :points_of_interest points_of_interest
                                               :special_type       special_type
-                                              :visibility_type    visibility_type
-                                              :fk_target_field_id fk_target_field_id}
+                                              :visibility_type    visibility_type}
                                              (when display_name {:display_name display_name}))))
+      ;; return updated field
       (Field id))))
 
 (defendpoint GET "/:id/summary"
diff --git a/src/metabase/models/field.clj b/src/metabase/models/field.clj
index c21d9f1be9e60569aab7b798b2166d31e1f6b236..43a788fce4a1d9c71e60ef0cbe94b1d250218535 100644
--- a/src/metabase/models/field.clj
+++ b/src/metabase/models/field.clj
@@ -46,8 +46,9 @@
     :UUIDField      ; e.g. a Postgres 'UUID' column
     :UnknownField})
 
-(def ^:const field-types
-  "Possible values for `Field.field_type`."
+(def ^:const ^:deprecated field-types
+  "Possible values for `Field.field_type`.
+   `field_type` is deprecated, and issue #2454 is open for removing it entirely. It's no longer displayed in the UI, nor is it possible to change its value from the API."
   #{:metric      ; A number that can be added, graphed, etc.
     :dimension   ; A high or low-cardinality numerical string value that is meant to be used as a grouping
     :info})      ; Non-numerical value that is not meant to be used
@@ -60,18 +61,20 @@
     :sensitive      ; Strict removal of field from all places except data model listing.  queries should error if someone attempts to access.
     :retired})      ; For fields that no longer exist in the physical db.  automatically set by Metabase.  QP should error if encountered in a query.
 
-(defn valid-metadata?
-  "Predicate function that checks if the set of metadata types for a field are sensible."
-  [base-type field-type special-type visibility-type]
-  (and (contains? base-types base-type)
-       (contains? field-types field-type)
-       (contains? visibility-types visibility-type)
-       (or (nil? special-type)
-           (contains? special-types special-type))
-       ;; this verifies that we have a numeric base-type when trying to use the unix timestamp transform (#824)
-       (or (nil? special-type)
-           (not (contains? #{:timestamp_milliseconds :timestamp_seconds} special-type))
-           (contains? #{:BigIntegerField :DecimalField :FloatField :IntegerField} base-type))))
+
+(def ^:const special-type->valid-base-types
+  "Map of special types to set of base types that are allowed to have that special type.
+   Special types not included in this map can be applied to any base type."
+  (let [numeric-base-types #{:BigIntegerField :DecimalField :FloatField :IntegerField}]
+    {:timestamp_seconds      numeric-base-types
+     :timestamp_milliseconds numeric-base-types}))
+
+(defn valid-special-type-for-base-type?
+  "Can SPECIAL-TYPE be used for this BASE-TYPE?"
+  ^Boolean [special-type base-type]
+  (let [valid-base-types (special-type->valid-base-types (keyword special-type))]
+    (or (not valid-base-types)
+        (contains? valid-base-types (keyword base-type)))))
 
 
 (i/defentity Field :metabase_field)
diff --git a/test/metabase/api/field_test.clj b/test/metabase/api/field_test.clj
index f198bf0f5c4ebf8dff6c0d4a97e1f5e794af458a..0cebd9f94091fd4843934383df9227d0e0aecb0b 100644
--- a/test/metabase/api/field_test.clj
+++ b/test/metabase/api/field_test.clj
@@ -1,6 +1,7 @@
 (ns metabase.api.field-test
   (:require [expectations :refer :all]
             [metabase.db :as db]
+            [metabase.driver :as driver]
             (metabase.models [database :refer [Database]]
                              [field :refer [Field]]
                              [field-values :refer [FieldValues]]
@@ -24,7 +25,7 @@
      :is_full_sync       true
      :organization_id    nil
      :description        nil
-     :features           (mapv name (metabase.driver/features (metabase.driver/engine->driver :h2)))}))
+     :features           (mapv name (driver/features (driver/engine->driver :h2)))}))
 
 
 ;; ## GET /api/field/:id
@@ -100,23 +101,7 @@
     :description     nil
     :special_type    nil
     :visibility_type :sensitive}]
-  (tu/with-temp* [Database [{database-id :id} {:name               "Field Test"
-                                               :engine             :yeehaw
-                                               :caveats            nil
-                                               :points_of_interest nil
-                                               :details            {}
-                                               :is_sample          false}]
-                  Table    [{table-id :id}    {:name   "Field Test"
-                                               :db_id  database-id
-                                               :active true}]
-                  Field    [{field-id :id}    {:table_id        table-id
-                                               :name            "Field Test"
-                                               :base_type       :TextField
-                                               :field_type      :info
-                                               :special_type    nil
-                                               :active          true
-                                               :preview_display true
-                                               :position        1}]]
+  (tu/with-temp* [Field [{field-id :id} {:name "Field Test"}]]
     (let [original-val (simple-field-details (Field field-id))]
       ;; set it
       ((user->client :crowberto) :put 200 (format "field/%d" field-id) {:name            "something else"
@@ -136,48 +121,36 @@
 (expect
   [true
    nil]
-  (tu/with-temp* [Database [{database-id :id} {:name               "Field Test"
-                                               :engine             :yeehaw
-                                               :caveats            nil
-                                               :points_of_interest nil
-                                               :details            {}
-                                               :is_sample          false}]
-                  Table    [{table-id :id}    {:name   "Field Test"
-                                               :db_id  database-id
-                                               :active true}]
-                  Field    [{field-id1 :id}   {:table_id        table-id
-                                               :name            "Target Field"
-                                               :base_type       :TextField
-                                               :field_type      :info
-                                               :special_type    :id
-                                               :active          true
-                                               :preview_display true
-                                               :position        1}]
-                  Field    [{field-id :id}    {:table_id           table-id
-                                               :name               "Field Test"
-                                               :base_type          :TextField
-                                               :field_type         :info
-                                               :special_type       :fk
-                                               :fk_target_field_id field-id1
-                                               :active             true
-                                               :preview_display    true
-                                               :position           1}]]
+  (tu/with-temp* [Field [{fk-field-id :id}]
+                  Field [{field-id :id}    {:special_type :fk, :fk_target_field_id fk-field-id}]]
     (let [original-val (boolean (db/select-one-field :fk_target_field_id Field, :id field-id))]
       ;; unset the :fk special-type
       ((user->client :crowberto) :put 200 (format "field/%d" field-id) {:special_type :name})
       [original-val
        (db/select-one-field :fk_target_field_id Field, :id field-id)])))
 
-;; check that you can't set a field to :timestamp_seconds if it's not of a proper base_type
+;; check that you can't set a field to :timestamp_seconds/:timestamp_milliseconds if it's not of a proper base_type
 (expect
-  ["Invalid Request."
+  ["Special type :timestamp_seconds cannot be used for fields with base type :TextField. Base type must be one of: #{:BigIntegerField :DecimalField :IntegerField :FloatField}"
    nil]
-  (tu/with-temp* [Database [{database-id :id}]
-                  Table    [{table-id :id} {:db_id database-id}]
-                  Field    [{field-id :id} {:table_id table-id}]]
+  (tu/with-temp* [Field [{field-id :id} {:base_type :TextField, :special_type nil}]]
     [((user->client :crowberto) :put 400 (str "field/" field-id) {:special_type :timestamp_seconds})
      (db/select-one-field :special_type Field, :id field-id)]))
 
+;; check that you *can* set it if it *is* the proper base type
+(expect
+  :timestamp_seconds
+  (tu/with-temp* [Field [{field-id :id} {:base_type :IntegerField}]]
+    ((user->client :crowberto) :put 200 (str "field/" field-id) {:special_type :timestamp_seconds})
+    (db/select-one-field :special_type Field, :id field-id)))
+
+;; check that a Field with a :base_type of :sensitive can still be modified as normal.
+;; This was a bug introduced when :visibility-type was added -- see issue #2678
+(expect
+  (tu/with-temp* [Field [{field-id :id} {:field_type "sensitive"}]]
+    (boolean ((user->client :crowberto) :put 200 (str "field/" field-id) {:special_type :avatar}))))
+
+
 
 (defn- field->field-values
   "Fetch the `FieldValues` object that corresponds to a given `Field`."
diff --git a/test/metabase/models/field_test.clj b/test/metabase/models/field_test.clj
index 8f3b702d3236c3b7163087e33ee38f7006f392a8..045e556aaed3d7f26e78f05836730d98c5a1fa1f 100644
--- a/test/metabase/models/field_test.clj
+++ b/test/metabase/models/field_test.clj
@@ -5,18 +5,14 @@
             [metabase.test.data :refer :all]))
 
 
-;; valid-metadata?
-(expect false (valid-metadata? nil nil nil nil))
-(expect false (valid-metadata? :IntegerField nil nil nil))
-(expect false (valid-metadata? :IntegerField :metric nil nil))
-(expect true (valid-metadata? :IntegerField :metric nil :normal))
-(expect false (valid-metadata? :foo :metric nil :normal))
-(expect false (valid-metadata? :IntegerField :foo nil :normal))
-(expect false (valid-metadata? :IntegerField :metric nil :foo))
-(expect true (valid-metadata? :IntegerField :metric :timestamp_seconds :normal))
-(expect true (valid-metadata? :IntegerField :metric :timestamp_milliseconds :normal))
-(expect false (valid-metadata? :DateTimeField :metric :timestamp_seconds :normal))
-(expect false (valid-metadata? :DateTimeField :metric :timestamp_milliseconds :normal))
+;; valid-special-type-for-base-type?
+(expect true  (valid-special-type-for-base-type? nil                     nil))
+(expect true  (valid-special-type-for-base-type? nil                     :foo))
+(expect true  (valid-special-type-for-base-type? nil                     :IntegerField))
+(expect true  (valid-special-type-for-base-type? :timestamp_seconds      :IntegerField))
+(expect true  (valid-special-type-for-base-type? :timestamp_milliseconds :IntegerField))
+(expect false (valid-special-type-for-base-type? :timestamp_seconds      :DateTimeField))
+(expect false (valid-special-type-for-base-type? :timestamp_milliseconds :DateTimeField))
 
 
 ;; field-should-have-field-values?