diff --git a/src/treq/client.py b/src/treq/client.py
index 7924d11..48fdc7f 100644
--- a/src/treq/client.py
+++ b/src/treq/client.py
@@ -143,12 +143,29 @@ class HTTPClient:
         kwargs.setdefault('_stacklevel', 3)
         return self.request('DELETE', url, **kwargs)
 
-    def request(self, method, url, **kwargs):
+    def request(
+        self,
+        method,
+        url,
+        *,
+        params=None,
+        headers=None,
+        data=None,
+        files=None,
+        json=_NOTHING,
+        auth=None,
+        cookies=None,
+        allow_redirects=True,
+        browser_like_redirects=False,
+        unbuffered=False,
+        reactor=None,
+        timeout=None,
+        _stacklevel=2,
+    ):
         """
         See :func:`treq.request()`.
         """
         method = method.encode('ascii').upper()
-        stacklevel = kwargs.pop('_stacklevel', 2)
 
         if isinstance(url, DecodedURL):
             parsed_url = url.encoded_url
@@ -163,7 +180,6 @@ class HTTPClient:
 
         # Join parameters provided in the URL
         # and the ones passed as argument.
-        params = kwargs.pop('params', None)
         if params:
             parsed_url = parsed_url.replace(
                 query=parsed_url.query + tuple(_coerced_query_params(params))
@@ -171,27 +187,20 @@ class HTTPClient:
 
         url = parsed_url.to_uri().to_text().encode('ascii')
 
-        headers = self._request_headers(kwargs.pop('headers', None), stacklevel + 1)
+        headers = self._request_headers(headers, _stacklevel + 1)
 
-        bodyProducer, contentType = self._request_body(
-            data=kwargs.pop('data', None),
-            files=kwargs.pop('files', None),
-            json=kwargs.pop('json', _NOTHING),
-            stacklevel=stacklevel + 1,
-        )
+        bodyProducer, contentType = self._request_body(data, files, json,
+                                                       stacklevel=_stacklevel + 1)
         if contentType is not None:
             headers.setRawHeaders(b'Content-Type', [contentType])
 
-        cookies = kwargs.pop('cookies', {})
-
         if not isinstance(cookies, CookieJar):
             cookies = cookiejar_from_dict(cookies)
 
         cookies = merge_cookies(self._cookiejar, cookies)
         wrapped_agent = CookieAgent(self._agent, cookies)
 
-        browser_like_redirects = kwargs.pop('browser_like_redirects', False)
-        if kwargs.pop('allow_redirects', True):
+        if allow_redirects:
             if browser_like_redirects:
                 wrapped_agent = BrowserLikeRedirectAgent(wrapped_agent)
             else:
@@ -200,7 +209,6 @@ class HTTPClient:
         wrapped_agent = ContentDecoderAgent(wrapped_agent,
                                             [(b'gzip', GzipDecoder)])
 
-        auth = kwargs.pop('auth', None)
         if auth:
             wrapped_agent = add_auth(wrapped_agent, auth)
 
@@ -208,10 +216,8 @@ class HTTPClient:
             method, url, headers=headers,
             bodyProducer=bodyProducer)
 
-        reactor = kwargs.pop('reactor', None)
         if reactor is None:
             from twisted.internet import reactor
-        timeout = kwargs.pop('timeout', None)
         if timeout:
             delayedCall = reactor.callLater(timeout, d.cancel)
 
@@ -222,20 +228,9 @@ class HTTPClient:
 
             d.addBoth(gotResult)
 
-        if not kwargs.pop('unbuffered', False):
+        if not unbuffered:
             d.addCallback(_BufferedResponse)
 
-        if kwargs:
-            warnings.warn(
-                (
-                    "Got unexpected keyword argument: {}."
-                    " treq will ignore this argument,"
-                    " but will raise TypeError in the next treq release."
-                ).format(", ".join(repr(k) for k in kwargs)),
-                DeprecationWarning,
-                stacklevel=stacklevel,
-            )
-
         return d.addCallback(_Response, cookies)
 
     def _request_headers(self, headers, stacklevel):

diff --git a/src/treq/test/test_api.py b/src/treq/test/test_api.py
index f7f9e09..2d9a1a1 100644
--- a/src/treq/test/test_api.py
+++ b/src/treq/test/test_api.py
@@ -101,25 +101,15 @@ class TreqAPITests(TestCase):
         This test verifies that stacklevel is set appropriately when issuing
         the warning.
         """
-        self.failureResultOf(
+        with self.assertRaises(TypeError) as c:
             treq.request(
                 "GET",
                 "https://foo.bar",
                 invalid=True,
                 pool=SyntacticAbominationHTTPConnectionPool(),
             )
-        )
 
-        [w] = self.flushWarnings([self.test_request_invalid_param])
-        self.assertEqual(DeprecationWarning, w["category"])
-        self.assertEqual(
-            (
-                "Got unexpected keyword argument: 'invalid'."
-                " treq will ignore this argument,"
-                " but will raise TypeError in the next treq release."
-            ),
-            w["message"],
-        )
+        self.assertIn("invalid", str(c.exception))
 
     def test_post_json_with_data(self):
         """
diff --git a/src/treq/test/test_client.py b/src/treq/test/test_client.py
index ee9f3fd..52897ce 100644
--- a/src/treq/test/test_client.py
+++ b/src/treq/test/test_client.py
@@ -655,19 +655,15 @@ class HTTPClientTests(TestCase):
 
     def test_request_invalid_param(self):
         """
-        `HTTPClient.request()` warns that invalid parameters are ignored and
-        that this is deprecated.
+        `HTTPClient.request()` rejects invalid keyword parameters with
+        `TypeError`.
         """
-        self.client.request('GET', b'http://example.com', invalid=True)
-
-        [w] = self.flushWarnings([self.test_request_invalid_param])
-        self.assertEqual(
-            (
-                "Got unexpected keyword argument: 'invalid'."
-                " treq will ignore this argument,"
-                " but will raise TypeError in the next treq release."
-            ),
-            w['message'],
+        self.assertRaises(
+            TypeError,
+            self.client.request,
+            "GET",
+            b"http://example.com",
+            invalid=True,
         )
 
     @with_clock
diff --git a/src/treq/test/test_multipart.py b/src/treq/test/test_multipart.py
index fd170e3..d193037 100644
--- a/src/treq/test/test_multipart.py
+++ b/src/treq/test/test_multipart.py
@@ -78,20 +78,26 @@ class MultiPartProducerTestCase(unittest.TestCase):
         passed as a parameter without either a C{seek} or C{tell} method,
         its C{length} attribute is set to C{UNKNOWN_LENGTH}.
         """
-        class HasSeek:
+        class CantTell:
             def seek(self, offset, whence):
-                pass
+                """
+                A C{seek} method that is never called because there is no
+                matching C{tell} method.
+                """
 
-        class HasTell:
+        class CantSeek:
             def tell(self):
-                pass
+                """
+                A C{tell} method that is never called because there is no
+                matching C{seek} method.
+                """
 
         producer = MultiPartProducer(
-            {"f": ("name", None, FileBodyProducer(HasSeek()))})
+            {"f": ("name", None, FileBodyProducer(CantTell()))})
         self.assertEqual(UNKNOWN_LENGTH, producer.length)
 
         producer = MultiPartProducer(
-            {"f": ("name", None, FileBodyProducer(HasTell()))})
+            {"f": ("name", None, FileBodyProducer(CantSeek()))})
         self.assertEqual(UNKNOWN_LENGTH, producer.length)
 
     def test_knownLengthOnFile(self):

public APIs should not swallow arbitrary invalid keyword arguments
This needs to be done in three steps:

- [x] emit a deprecation warning when emitting unused keyword arguments
- [x] do a release with the deprecation warning
- [x] remove the `**kwargs` entirely

#251 was both the initial report of this as well as a proposed implementation, but its author lost interest.
src/treq/test/test_api.py::TreqAPITests::test_request_invalid_param src/treq/test/test_client.py::HTTPClientTests::test_request_invalid_param
src/treq/test/test_api.py::TreqAPITests::test_cached_pool src/treq/test/test_api.py::TreqAPITests::test_custom_agent src/treq/test/test_api.py::TreqAPITests::test_custom_pool src/treq/test/test_api.py::TreqAPITests::test_default_pool src/treq/test/test_api.py::TreqAPITests::test_post_json_with_data src/treq/test/test_api.py::DefaultReactorTests::test_passes_reactor src/treq/test/test_api.py::DefaultReactorTests::test_uses_default_reactor src/treq/test/test_api.py::DefaultPoolTests::test_cached_global_pool src/treq/test/test_api.py::DefaultPoolTests::test_persistent_false src/treq/test/test_api.py::DefaultPoolTests::test_persistent_false_new src/treq/test/test_api.py::DefaultPoolTests::test_persistent_false_not_stored src/treq/test/test_api.py::DefaultPoolTests::test_pool_none_persistent_none src/treq/test/test_api.py::DefaultPoolTests::test_pool_none_persistent_true src/treq/test/test_api.py::DefaultPoolTests::test_specified_pool src/treq/test/test_client.py::HTTPClientTests::test_post src/treq/test/test_client.py::HTTPClientTests::test_request_browser_like_redirects src/treq/test/test_client.py::HTTPClientTests::test_request_case_insensitive_methods src/treq/test/test_client.py::HTTPClientTests::test_request_data_dict src/treq/test/test_client.py::HTTPClientTests::test_request_data_file src/treq/test/test_client.py::HTTPClientTests::test_request_data_single_dict src/treq/test/test_client.py::HTTPClientTests::test_request_data_tuple src/treq/test/test_client.py::HTTPClientTests::test_request_dict_headers src/treq/test/test_client.py::HTTPClientTests::test_request_dict_headers_invalid_values src/treq/test/test_client.py::HTTPClientTests::test_request_dict_single_value_query_params src/treq/test/test_client.py::HTTPClientTests::test_request_files_tuple_too_long src/treq/test/test_client.py::HTTPClientTests::test_request_files_tuple_too_short src/treq/test/test_client.py::HTTPClientTests::test_request_headers_invalid_type src/treq/test/test_client.py::HTTPClientTests::test_request_headers_object src/treq/test/test_client.py::HTTPClientTests::test_request_json_bool src/treq/test/test_client.py::HTTPClientTests::test_request_json_dict src/treq/test/test_client.py::HTTPClientTests::test_request_json_none src/treq/test/test_client.py::HTTPClientTests::test_request_json_number src/treq/test/test_client.py::HTTPClientTests::test_request_json_string src/treq/test/test_client.py::HTTPClientTests::test_request_json_tuple src/treq/test/test_client.py::HTTPClientTests::test_request_json_with_data src/treq/test/test_client.py::HTTPClientTests::test_request_json_with_files src/treq/test/test_client.py::HTTPClientTests::test_request_merge_query_params src/treq/test/test_client.py::HTTPClientTests::test_request_merge_tuple_query_params src/treq/test/test_client.py::HTTPClientTests::test_request_mixed_params src/treq/test/test_client.py::HTTPClientTests::test_request_mixed_params_dict src/treq/test/test_client.py::HTTPClientTests::test_request_named_attachment src/treq/test/test_client.py::HTTPClientTests::test_request_named_attachment_and_ctype src/treq/test/test_client.py::HTTPClientTests::test_request_no_name_attachment src/treq/test/test_client.py::HTTPClientTests::test_request_post_redirect_denied src/treq/test/test_client.py::HTTPClientTests::test_request_query_param_seps src/treq/test/test_client.py::HTTPClientTests::test_request_query_params src/treq/test/test_client.py::HTTPClientTests::test_request_timeout_cancelled src/treq/test/test_client.py::HTTPClientTests::test_request_timeout_fired src/treq/test/test_client.py::HTTPClientTests::test_request_tuple_query_param_coercion src/treq/test/test_client.py::HTTPClientTests::test_request_tuple_query_value_coercion src/treq/test/test_client.py::HTTPClientTests::test_request_tuple_query_values src/treq/test/test_client.py::HTTPClientTests::test_request_unsupported_params_combination src/treq/test/test_client.py::HTTPClientTests::test_request_uri_bytes_pass src/treq/test/test_client.py::HTTPClientTests::test_request_uri_decodedurl src/treq/test/test_client.py::HTTPClientTests::test_request_uri_encodedurl src/treq/test/test_client.py::HTTPClientTests::test_request_uri_hyperlink_params src/treq/test/test_client.py::HTTPClientTests::test_request_uri_idn src/treq/test/test_client.py::HTTPClientTests::test_request_uri_idn_params src/treq/test/test_client.py::HTTPClientTests::test_request_uri_plus_pass src/treq/test/test_client.py::HTTPClientTests::test_response_buffering_is_disabled_with_unbufferred_arg src/treq/test/test_client.py::HTTPClientTests::test_response_is_buffered src/treq/test/test_client.py::BodyBufferingProtocolTests::test_buffers_data src/treq/test/test_client.py::BodyBufferingProtocolTests::test_fires_finished_deferred src/treq/test/test_client.py::BodyBufferingProtocolTests::test_propagates_data_to_destination src/treq/test/test_client.py::BodyBufferingProtocolTests::test_propogates_connectionLost_reason src/treq/test/test_client.py::BufferedResponseTests::test_concurrent_receivers src/treq/test/test_client.py::BufferedResponseTests::test_receiver_after_finished src/treq/test/test_client.py::BufferedResponseTests::test_wraps_protocol src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_defaultCooperator src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_failOnByteStrings src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_failOnUnknownParams src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_failedReadWhileProducing src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_fieldsAndAttachment src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_inputClosedAtEOF src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_interface src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_knownLengthOnFile src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_missingAttachmentName src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_multipleFieldsAndAttachments src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_newLinesInParams src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_pauseProducing src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_resumeProducing src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_startProducing src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_stopProducing src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_twoFields src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_unicodeAttachmentName src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_unicodeString src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_unknownLength src/treq/test/test_multipart.py::MultiPartProducerTestCase::test_worksWithCgi src/treq/test/test_multipart.py::LengthConsumerTestCase::test_scalarsUpdateCounter src/treq/test/test_multipart.py::LengthConsumerTestCase::test_stringUpdatesCounter