diff --git a/tests/test_casing.py b/tests/test_casing.py
index fb7bd89..57ea9be 100644
--- a/tests/test_casing.py
+++ b/tests/test_casing.py
@@ -1,6 +1,6 @@
 import pytest
 
-from worf.casing import camel_to_snake, snake_to_camel, whitespace_to_camel
+from worf.casing import camel_to_snake, snake_to_camel
 from worf.exceptions import NamingThingsError
 
 
@@ -39,7 +39,3 @@ def test_camel_to_snake_catches_invalid_chars():
 def test_snake_to_camel_catches_invalid_chars():
     with pytest.raises(NamingThingsError):
         snake_to_camel("this_aint_no_🐍")
-
-
-def test_whitespace_to_camel():
-    assert "thisIsAVerboseName" == whitespace_to_camel("This is a verbose name")
diff --git a/worf/casing.py b/worf/casing.py
index 414fa1c..d428472 100644
--- a/worf/casing.py
+++ b/worf/casing.py
@@ -37,12 +37,3 @@ def camel_to_snake(camel):
         last_was_upper = value.isupper()
 
     return snake
-
-
-def whitespace_to_camel(string):
-    pos = string.find(" ")
-    if pos == -1:
-        return string[:1].lower() + string[1:]
-
-    new_string = string[:pos] + string[pos + 1 :].capitalize()
-    return whitespace_to_camel(new_string)
diff --git a/worf/validators.py b/worf/validators.py
index 3897a52..87cb741 100644
--- a/worf/validators.py
+++ b/worf/validators.py
@@ -8,7 +8,6 @@
 from django.utils.dateparse import parse_datetime
 
 from worf.exceptions import NotImplementedInWorfYet
-from worf.casing import snake_to_camel
 
 
 class ValidationMixin:
@@ -36,7 +35,7 @@ def _validate_boolean(self, key):
 
         if not isinstance(coerced, bool):
             raise ValidationError(
-                f"Field {snake_to_camel(key)} accepts a boolean, got {value}, coerced to {coerced}"
+                f"Field {self.keymap[key]} accepts a boolean, got {value}, coerced to {coerced}"
             )
 
         return coerced
@@ -58,7 +57,7 @@ def _validate_datetime(self, key):
 
         if not isinstance(coerced, datetime):
             raise ValidationError(
-                f"Field {snake_to_camel(key)} accepts a iso datetime string, got {value}, coerced to {coerced}"
+                f"Field {self.keymap[key]} accepts a iso datetime string, got {value}, coerced to {coerced}"
             )
 
         return coerced
@@ -68,18 +67,18 @@ def _validate_many_to_many(self, key):
 
         if not isinstance(value, list):
             raise ValidationError(
-                f"Field {snake_to_camel(key)} accepts an array, got {type(value)} {value}"
+                f"Field {self.keymap[key]} accepts an array, got {type(value)} {value}"
             )
 
     def _validate_string(self, key, max_length):
         value = self.bundle[key]
 
         if not isinstance(value, str):
-            raise ValidationError(f"Field {snake_to_camel(key)} accepts string")
+            raise ValidationError(f"Field {self.keymap[key]} accepts string")
 
         if max_length is not None and len(value) > max_length:
             raise ValidationError(
-                f"Field {snake_to_camel(key)} accepts a maximum of {max_length} characters"
+                f"Field {self.keymap[key]} accepts a maximum of {max_length} characters"
             )
 
         return value
@@ -93,7 +92,7 @@ def _validate_int(self, key):
         try:
             integer = int(value)
         except (TypeError, ValueError):
-            raise ValidationError(f"Field {snake_to_camel(key)} accepts an integer")
+            raise ValidationError(f"Field {self.keymap[key]} accepts an integer")
 
         return integer
 
@@ -102,7 +101,7 @@ def _validate_positive_int(self, key):
 
         if integer < 0:
             raise ValidationError(
-                f"Field {snake_to_camel(key)} accepts a positive integer"
+                f"Field {self.keymap[key]} accepts a positive integer"
             )
 
         return integer
@@ -113,7 +112,7 @@ def coerce_array_of_integers(self, key):
         try:
             self.bundle[key] = [int(id) for id in self.bundle[key]]
         except ValueError:
-            message = f"Field {snake_to_camel(key)} accepts an array of integers. Got {self.bundle[key]} instead."
+            message = f"Field {self.keymap[key]} accepts an array of integers. Got {self.bundle[key]} instead."
             raise ValidationError(message + " I couldn't coerce the values.")
 
     def validate_int(self, value):
@@ -167,7 +166,7 @@ def validate_bundle(self, key):
         serializer = self.get_serializer()
 
         if self.request.method in ("PATCH", "PUT") and key not in serializer.write():
-            message = f"{snake_to_camel(key)} is not editable"
+            message = f"{self.keymap[key]} is not editable"
             if settings.DEBUG:
                 message += f":: {serializer}"
             raise ValidationError(message)
@@ -179,7 +178,7 @@ def validate_bundle(self, key):
         )
 
         if not hasattr(self.model, key) and not annotation:
-            raise ValidationError(f"{snake_to_camel(key)} does not exist")
+            raise ValidationError(f"{self.keymap[key]} does not exist")
 
         if key not in self.secure_fields and isinstance(self.bundle[key], str):
             self.bundle[key] = self.bundle[key].strip()
@@ -234,7 +233,7 @@ def validate_bundle(self, key):
             # try:
             #     json.loads(self.bundle[key])
             # except ValueError:
-            #     raise ValidationError(f"Field {snake_to_camel(key)} requires valid JSON")
+            #     raise ValidationError(f"Field {self.keymap[key]} requires valid JSON")
 
         else:
             message = f"{field.get_internal_type()} has no validation method for {key}"
diff --git a/worf/views/base.py b/worf/views/base.py
index 4ab390f..8be2b19 100644
--- a/worf/views/base.py
+++ b/worf/views/base.py
@@ -17,7 +17,7 @@
 from django.views.decorators.cache import never_cache
 from django.utils.decorators import method_decorator
 
-from worf.casing import camel_to_snake, whitespace_to_camel
+from worf.casing import camel_to_snake, snake_to_camel
 from worf.exceptions import HTTP_EXCEPTIONS, HTTP404, HTTP422, PermissionsException
 from worf.serializers import LegacySerializer
 from worf.validators import ValidationMixin
@@ -89,7 +89,8 @@ def __init__(self, *args, **kwargs):
     def name(self):
         if isinstance(self.payload_key, str):
             return self.payload_key
-        return whitespace_to_camel(self.model._meta.verbose_name_plural)
+        verbose_name_plural = self.model._meta.verbose_name_plural
+        return snake_to_camel(verbose_name_plural.replace(" ", "_").lower())
 
     def _check_permissions(self):
         """Return a permissions exception when in debug mode instead of 404."""
@@ -185,31 +186,24 @@ def set_bundle_from_querystring(self):
         # parse_qs gives us a dictionary where all values are lists
         qs = parse_qs(self.request.META["QUERY_STRING"])
 
-        # TODO: TLDR; Switch to POST for search instead of GET/querystring params.
-        # we want to preserve strings wherever there are not duplicate keys
-        # Step through the list and construct a dictionary for all fields
-        # that are not duplicated
-
-        # fundamentally all urlparams are treated as arrays natively.
-        # we can't enforce type coersion here...
-
-        # we can't assume everything is an array or everything is not an array
-        # when it's a querystring
-
         raw_bundle = {}
+
         for key, value in qs.items():
             raw_bundle[key] = value[0] if len(value) == 1 else value
 
         self.set_bundle(raw_bundle)
 
-    def set_bundle(self, raw):
+    def set_bundle(self, raw_bundle):
         self.bundle = {}
-        if not raw:
+        self.keymap = {}
+
+        if not raw_bundle:
             return  # No need to loop or set self.bundle again if it's empty
 
-        for key in raw.keys():
+        for key in raw_bundle.keys():
             field = camel_to_snake(key)
-            self.bundle[field] = raw[key]
+            self.bundle[field] = raw_bundle[key]
+            self.keymap[field] = key
 
     def dispatch(self, request, *args, **kwargs):
         method = request.method.lower()
diff --git a/worf/views/create.py b/worf/views/create.py
index 2b4c956..e4bb2ce 100644
--- a/worf/views/create.py
+++ b/worf/views/create.py
@@ -1,3 +1,5 @@
+from django.core.exceptions import ValidationError
+
 from worf.assigns import AssignAttributes
 from worf.views.base import AbstractBaseAPI
 
@@ -17,3 +19,20 @@ def post(self, request, *args, **kwargs):
         new_instance = self.create()
         serializer = self.get_serializer()
         return self.render_to_response(serializer.read(new_instance), 201)
+
+    def create(self):
+        self.validate()
+
+        return self.model.objects.create(**self.bundle)
+
+    def validate(self):
+        create_fields = self.get_serializer().create()
+
+        for key in self.bundle.keys():
+            self.validate_bundle(key)
+            # ignore create_fields for now if it's empty
+            # this should be moved into validate bundle
+            if create_fields and key not in create_fields:
+                raise ValidationError(
+                    f"{self.keymap[key]} not allowed when creating {self.name}"
+                )