diff --git a/google/resumable_media/_upload.py b/google/resumable_media/_upload.py
index b4eac56..c681cc0 100644
--- a/google/resumable_media/_upload.py
+++ b/google/resumable_media/_upload.py
@@ -794,14 +794,8 @@ class ResumableUpload(UploadBase):
             The headers **do not** incorporate the ``_headers`` on the
             current instance.
 
-        Raises:
-            ValueError: If the current upload is not in an invalid state.
-
         .. _sans-I/O: https://sans-io.readthedocs.io/
         """
-        if not self.invalid:
-            raise ValueError("Upload is not in invalid state, no need to recover.")
-
         headers = {_helpers.CONTENT_RANGE_HEADER: "bytes */*"}
         return _PUT, self.resumable_url, None, headers
 
diff --git a/google/resumable_media/requests/upload.py b/google/resumable_media/requests/upload.py
index b565d7a..0649001 100644
--- a/google/resumable_media/requests/upload.py
+++ b/google/resumable_media/requests/upload.py
@@ -517,14 +517,15 @@ class ResumableUpload(_request_helpers.RequestsMixin, _upload.ResumableUpload):
         )
 
     def recover(self, transport):
-        """Recover from a failure.
-
-        This method should be used when a :class:`ResumableUpload` is in an
-        :attr:`~ResumableUpload.invalid` state due to a request failure.
+        """Recover from a failure and check the status of the current upload.
 
         This will verify the progress with the server and make sure the
         current upload is in a valid state before :meth:`transmit_next_chunk`
-        can be used again.
+        can be used again. See https://cloud.google.com/storage/docs/performing-resumable-uploads#status-check
+        for more information.
+
+        This method can be used when a :class:`ResumableUpload` is in an
+        :attr:`~ResumableUpload.invalid` state due to a request failure.
 
         Args:
             transport (~requests.Session): A ``requests`` object which can

diff --git a/tests/unit/test__upload.py b/tests/unit/test__upload.py
index 110ed77..db943d9 100644
--- a/tests/unit/test__upload.py
+++ b/tests/unit/test__upload.py
@@ -951,8 +951,13 @@ class TestResumableUpload(object):
         upload = _upload.ResumableUpload(RESUMABLE_URL, ONE_MB)
         assert not upload.invalid
 
-        with pytest.raises(ValueError):
-            upload._prepare_recover_request()
+        method, url, payload, headers = upload._prepare_recover_request()
+        assert method == "PUT"
+        assert url == upload.resumable_url
+        assert payload is None
+        assert headers == {"content-range": "bytes */*"}
+        # Make sure headers are untouched.
+        assert upload._headers == {}
 
     def test__prepare_recover_request(self):
         upload = _upload.ResumableUpload(RESUMABLE_URL, ONE_MB)

After retry limit reached, ResumableUpload.transmit_next_chunk propagates exception with invalid==False
The code below with comments probably best describes the issue... it shows two workarounds. A third workaround (not shown) might be to look for a retry timeout exception and assume the ResumableUpload is invalid despite it being in a recoverable state in my various tests. I'm wondering if a call to _make_invalid upon final retry limit reached is reasonable, or perhaps documenting implied invalidity after retry limit though the latter would seem odd since a retry limit having been reached is not equivalent to a non-recoverable state. 

```
upload = ResumableUpload(upload_url, chunk_size)
response = upload.initiate(transport=session, 
                            stream=stream, 
                            metadata=metadata, 
                            content_type="application/octet-stream", 
                            stream_final=False)
while not upload.finished:
    try:
        pos_before_xmit = stream.tell()
        upload.transmit_next_chunk(session)
    except Exception as ex:
        bytes_attempted = stream.tell() - pos_before_xmit
        """ 
        #
        # I first tried the following which skips recover
        # and jumps back to transmit_next_chunk above.
        # Without this, transmit_next_chunk fails. This
        # seems somewhat equivalent to continuing
        # above with retry reset.
        #
        if not upload.invalid:
            stream.seek(pos_before_xmit, io.SEEK_SET)
        else:
            r = upload.recover(session)
        """
        #
        # After looking at internals, I tried this which
        # also worked. Without the _make_invalid call,
        # recover fails and the stream is not rewound,
        # causing transmit_next_chunk to fail. 
        #
        if not upload.invalid:
            upload._make_invalid() # not really supposed to be called by users.
        r = upload.recover(session)
        # etc.
```

An aside, thanks to authors/contribs who created ResumableUpload.
tests/unit/test__upload.py::TestResumableUpload::test__prepare_recover_request_not_invalid
tests/unit/test__upload.py::TestUploadBase::test_constructor_defaults tests/unit/test__upload.py::TestUploadBase::test_constructor_explicit tests/unit/test__upload.py::TestUploadBase::test_finished_property tests/unit/test__upload.py::TestUploadBase::test__process_response_bad_status tests/unit/test__upload.py::TestUploadBase::test__process_response tests/unit/test__upload.py::TestUploadBase::test__get_status_code tests/unit/test__upload.py::TestUploadBase::test__get_headers tests/unit/test__upload.py::TestUploadBase::test__get_body tests/unit/test__upload.py::TestSimpleUpload::test__prepare_request_already_finished tests/unit/test__upload.py::TestSimpleUpload::test__prepare_request_non_bytes_data tests/unit/test__upload.py::TestSimpleUpload::test__prepare_request tests/unit/test__upload.py::TestSimpleUpload::test__prepare_request_with_headers tests/unit/test__upload.py::TestSimpleUpload::test_transmit tests/unit/test__upload.py::TestMultipartUpload::test_constructor_defaults tests/unit/test__upload.py::TestMultipartUpload::test_constructor_explicit tests/unit/test__upload.py::TestMultipartUpload::test__prepare_request_already_finished tests/unit/test__upload.py::TestMultipartUpload::test__prepare_request_non_bytes_data tests/unit/test__upload.py::TestMultipartUpload::test__prepare_request tests/unit/test__upload.py::TestMultipartUpload::test__prepare_request_with_headers tests/unit/test__upload.py::TestMultipartUpload::test__prepare_request_with_checksum[md5] tests/unit/test__upload.py::TestMultipartUpload::test__prepare_request_with_checksum[crc32c] tests/unit/test__upload.py::TestMultipartUpload::test__prepare_request_with_checksum_overwrite[md5] tests/unit/test__upload.py::TestMultipartUpload::test__prepare_request_with_checksum_overwrite[crc32c] tests/unit/test__upload.py::TestMultipartUpload::test_transmit tests/unit/test__upload.py::TestResumableUpload::test_constructor tests/unit/test__upload.py::TestResumableUpload::test_constructor_bad_chunk_size tests/unit/test__upload.py::TestResumableUpload::test_invalid_property tests/unit/test__upload.py::TestResumableUpload::test_chunk_size_property tests/unit/test__upload.py::TestResumableUpload::test_resumable_url_property tests/unit/test__upload.py::TestResumableUpload::test_bytes_uploaded_property tests/unit/test__upload.py::TestResumableUpload::test_total_bytes_property tests/unit/test__upload.py::TestResumableUpload::test__prepare_initiate_request tests/unit/test__upload.py::TestResumableUpload::test_prepare_initiate_request_with_signed_url tests/unit/test__upload.py::TestResumableUpload::test__prepare_initiate_request_with_headers tests/unit/test__upload.py::TestResumableUpload::test__prepare_initiate_request_known_size tests/unit/test__upload.py::TestResumableUpload::test__prepare_initiate_request_unknown_size tests/unit/test__upload.py::TestResumableUpload::test__prepare_initiate_request_already_initiated tests/unit/test__upload.py::TestResumableUpload::test__prepare_initiate_request_bad_stream_position tests/unit/test__upload.py::TestResumableUpload::test__process_initiate_response_non_200 tests/unit/test__upload.py::TestResumableUpload::test__process_initiate_response tests/unit/test__upload.py::TestResumableUpload::test_initiate tests/unit/test__upload.py::TestResumableUpload::test__prepare_request_already_finished tests/unit/test__upload.py::TestResumableUpload::test__prepare_request_invalid tests/unit/test__upload.py::TestResumableUpload::test__prepare_request_not_initiated tests/unit/test__upload.py::TestResumableUpload::test__prepare_request_invalid_stream_state tests/unit/test__upload.py::TestResumableUpload::test__prepare_request_success tests/unit/test__upload.py::TestResumableUpload::test__prepare_request_success_with_headers tests/unit/test__upload.py::TestResumableUpload::test__prepare_request_with_checksum[md5] tests/unit/test__upload.py::TestResumableUpload::test__prepare_request_with_checksum[crc32c] tests/unit/test__upload.py::TestResumableUpload::test__update_checksum[md5] tests/unit/test__upload.py::TestResumableUpload::test__update_checksum[crc32c] tests/unit/test__upload.py::TestResumableUpload::test__update_checksum_rewind[md5] tests/unit/test__upload.py::TestResumableUpload::test__update_checksum_rewind[crc32c] tests/unit/test__upload.py::TestResumableUpload::test__update_checksum_none tests/unit/test__upload.py::TestResumableUpload::test__update_checksum_invalid tests/unit/test__upload.py::TestResumableUpload::test__make_invalid tests/unit/test__upload.py::TestResumableUpload::test__process_resumable_response_bad_status tests/unit/test__upload.py::TestResumableUpload::test__process_resumable_response_success tests/unit/test__upload.py::TestResumableUpload::test__process_resumable_response_partial_no_range tests/unit/test__upload.py::TestResumableUpload::test__process_resumable_response_partial_bad_range tests/unit/test__upload.py::TestResumableUpload::test__process_resumable_response_partial tests/unit/test__upload.py::TestResumableUpload::test__validate_checksum_success[md5] tests/unit/test__upload.py::TestResumableUpload::test__validate_checksum_success[crc32c] tests/unit/test__upload.py::TestResumableUpload::test__validate_checksum_none tests/unit/test__upload.py::TestResumableUpload::test__validate_checksum_header_no_match[md5] tests/unit/test__upload.py::TestResumableUpload::test__validate_checksum_header_no_match[crc32c] tests/unit/test__upload.py::TestResumableUpload::test__validate_checksum_mismatch[md5] tests/unit/test__upload.py::TestResumableUpload::test__validate_checksum_mismatch[crc32c] tests/unit/test__upload.py::TestResumableUpload::test_transmit_next_chunk tests/unit/test__upload.py::TestResumableUpload::test__prepare_recover_request tests/unit/test__upload.py::TestResumableUpload::test__prepare_recover_request_with_headers tests/unit/test__upload.py::TestResumableUpload::test__process_recover_response_bad_status tests/unit/test__upload.py::TestResumableUpload::test__process_recover_response_no_range tests/unit/test__upload.py::TestResumableUpload::test__process_recover_response_bad_range tests/unit/test__upload.py::TestResumableUpload::test__process_recover_response_with_range tests/unit/test__upload.py::TestResumableUpload::test_recover tests/unit/test__upload.py::test_get_boundary tests/unit/test__upload.py::Test_construct_multipart_request::test_binary tests/unit/test__upload.py::Test_construct_multipart_request::test_unicode tests/unit/test__upload.py::test_get_total_bytes tests/unit/test__upload.py::Test_get_next_chunk::test_exhausted_known_size tests/unit/test__upload.py::Test_get_next_chunk::test_exhausted_known_size_zero tests/unit/test__upload.py::Test_get_next_chunk::test_exhausted_known_size_zero_nonempty tests/unit/test__upload.py::Test_get_next_chunk::test_success_known_size_lt_stream_size tests/unit/test__upload.py::Test_get_next_chunk::test_success_known_size tests/unit/test__upload.py::Test_get_next_chunk::test_success_unknown_size tests/unit/test__upload.py::Test_get_content_range::test_known_size tests/unit/test__upload.py::Test_get_content_range::test_unknown_size