diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 0ac31c9..c982642 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -7,7 +7,9 @@ This document describes changes between each past release.
 10.2.0 (unreleased)
 ===================
 
-- Nothing changed yet.
+**New feature**
+
+- Created new method on client to get paginated records ``get_paginated_records``. (#175)
 
 
 10.1.1 (2018-11-13)
diff --git a/README.rst b/README.rst
index fa79a02..2052551 100644
--- a/README.rst
+++ b/README.rst
@@ -265,6 +265,11 @@ A record is a dict with the "permissions" and "data" keys.
     # Or retrieve all records.
     records = client.get_records(collection='todos', bucket='default')
 
+   # Or retrieve records page by page.
+   for page in client.get_paginated_records(collection='todos', bucket='default'):
+      # Do something with each page
+      print(page)
+
     # Or retrieve records timestamp.
     records_timestamp = client.get_records_timestamp(collection='todos', bucket='default')
 
diff --git a/kinto_http/__init__.py b/kinto_http/__init__.py
index c39e322..f8e8700 100644
--- a/kinto_http/__init__.py
+++ b/kinto_http/__init__.py
@@ -655,6 +655,29 @@ class Client(object):
                                      collection=collection)
         return self._paginated(endpoint, **kwargs)
 
+    def get_paginated_records(self, *, collection=None, bucket=None, **kwargs):
+        endpoint = self.get_endpoint('records',
+                                     bucket=bucket,
+                                     collection=collection)
+
+        return self._paginated_generator(endpoint, **kwargs)
+
+    def _paginated_generator(self, endpoint, *, if_none_match=None, **kwargs):
+        headers = {}
+        if if_none_match is not None:
+            headers['If-None-Match'] = utils.quote(if_none_match)
+
+        record_resp, headers = self.session.request(
+            'get', endpoint, headers=headers, params=kwargs)
+
+        if record_resp:
+            yield record_resp
+
+        if 'next-page' in map(str.lower, headers.keys()):
+            next_page = headers['Next-Page']
+            yield from self._paginated_generator(next_page,
+                                                 if_none_match=if_none_match)
+
     def get_record(self, *, id, collection=None, bucket=None):
         endpoint = self.get_endpoint('record', id=id,
                                      bucket=bucket,

Expose methods for paginated lists
Currently there is no way to consume the content of individual pages using `get_records()`.

We could add a generator `get_paginated_records()` that would yield individual pages...

```py
for page_records in client.get_paginated_records(_limit=100):
     print(len(page_records))
```
diff --git a/kinto_http/tests/functional.py b/kinto_http/tests/functional.py
index e2351ee..15bc5c7 100644
--- a/kinto_http/tests/functional.py
+++ b/kinto_http/tests/functional.py
@@ -311,6 +311,19 @@ class FunctionalTest(unittest.TestCase):
         records = client.get_records()
         assert len(records) == 10
 
+    def test_records_generator_retrieval(self):
+        client = Client(server_url=self.server_url, auth=self.auth,
+                        bucket='mozilla', collection='payments')
+        client.create_bucket()
+        client.create_collection()
+        for i in range(10):
+            client.create_record(data={'foo': 'bar'},
+                                 permissions={'read': ['account:alexis']})
+
+        pages = list(client.get_paginated_records())
+
+        assert len(pages) == 2
+
     def test_single_record_save(self):
         client = Client(server_url=self.server_url, auth=self.auth,
                         bucket='mozilla', collection='payments')
diff --git a/kinto_http/tests/test_client.py b/kinto_http/tests/test_client.py
index 7d45230..f737096 100644
--- a/kinto_http/tests/test_client.py
+++ b/kinto_http/tests/test_client.py
@@ -833,6 +833,36 @@ class RecordTest(unittest.TestCase):
             {'id': '6', 'value': 'item6'},
         ]
 
+    def test_pagination_is_followed_generator(self):
+        # Mock the calls to request.
+        link = ('http://example.org/buckets/buck/collections/coll/records/'
+                '?token=1234')
+
+        response = [
+            # First one returns a list of items with a pagination token.
+            build_response(
+                [{'id': '1', 'value': 'item1'},
+                 {'id': '2', 'value': 'item2'}, ],
+                {'Next-Page': link}),
+            build_response(
+                [{'id': '3', 'value': 'item3'},
+                 {'id': '4', 'value': 'item4'}, ],
+                {'Next-Page': link}),
+            # Second one returns a list of items without a pagination token.
+            build_response(
+                [{'id': '5', 'value': 'item5'},
+                 {'id': '6', 'value': 'item6'}, ],
+            ),
+        ]
+
+        self.session.request.side_effect = response
+
+        # Build repsonses for assertion without next page
+        response = [record[0] for record in response]
+
+        for index, page_records in enumerate(self.client.get_paginated_records()):
+            assert response[index] == page_records
+
     def test_pagination_is_followed_for_number_of_pages(self):
         # Mock the calls to request.
         link = ('http://example.org/buckets/buck/collections/coll/records/'
@@ -911,6 +941,41 @@ class RecordTest(unittest.TestCase):
         self.session.request.assert_any_call(
             'get', link, headers={'If-None-Match': '"1234"'}, params={})
 
+    def test_pagination_generator_if_none_match(self):
+        link = ('http://example.org/buckets/buck/collections/coll/records/'
+                '?token=1234')
+
+        response = [
+            # First one returns a list of items with a pagination token.
+            build_response(
+                [{'id': '1', 'value': 'item1'},
+                 {'id': '2', 'value': 'item2'}, ],
+                {'Next-Page': link}),
+            build_response(
+                [{'id': '3', 'value': 'item3'},
+                 {'id': '4', 'value': 'item4'}, ],
+                {'Next-Page': link}),
+            # Second one returns a list of items without a pagination token.
+            build_response(
+                [{'id': '5', 'value': 'item5'},
+                 {'id': '6', 'value': 'item6'}, ],
+            ),
+        ]
+
+        self.session.request.side_effect = response
+
+        # Build repsonses for assertion without next page
+        response = [record[0] for record in response]
+
+        for index, page_records in enumerate(self.client.get_paginated_records(
+                                             if_none_match="1234")):
+            # Check that the If-None-Match header is present in the requests.
+            assert response[index] == page_records
+
+        # Check that the If-None-Match header is present in the requests.
+        self.session.request.assert_any_call(
+            'get', link, headers={'If-None-Match': '"1234"'}, params={})
+
     def test_collection_can_delete_a_record(self):
         mock_response(self.session, data={'id': 1234})
         resp = self.client.delete_record(id=1234)

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest numpy>=1.16.0 pandas>=1.0.0
apt-get update apt-get install -y gcc libffi-dev libssl-dev
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
certifi==2018.11.29
chardet==3.0.4
exceptiongroup==1.2.2
idna==2.7
iniconfig==2.1.0
-e git+https://github.com/Kinto/kinto-http.py.git@de4fcceefd7b4794093ed53899f2212a29e27ecc#egg=kinto_http
numpy==2.0.2
packaging==24.2
pandas==2.2.3
pluggy==1.5.0
pytest==8.3.5
python-dateutil==2.9.0.post0
pytz==2025.2
requests==2.20.1
six==1.17.0
tomli==2.2.1
tzdata==2025.2
Unidecode==1.0.23
urllib3==1.24.1

name: kinto-http.py
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - certifi==2018.11.29
      - chardet==3.0.4
      - exceptiongroup==1.2.2
      - idna==2.7
      - iniconfig==2.1.0
      - numpy==2.0.2
      - packaging==24.2
      - pandas==2.2.3
      - pluggy==1.5.0
      - pytest==8.3.5
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - requests==2.20.1
      - six==1.17.0
      - tomli==2.2.1
      - tzdata==2025.2
      - unidecode==1.0.23
      - urllib3==1.24.1
prefix: /opt/conda/envs/kinto-http.py

kinto_http/tests/test_client.py::RecordTest::test_pagination_generator_if_none_match kinto_http/tests/test_client.py::RecordTest::test_pagination_is_followed_generator
kinto_http/tests/functional.py::FunctionalTest::test_bucket_creation kinto_http/tests/functional.py::FunctionalTest::test_bucket_creation_if_not_exists kinto_http/tests/functional.py::FunctionalTest::test_bucket_deletion kinto_http/tests/functional.py::FunctionalTest::test_bucket_deletion_if_exists kinto_http/tests/functional.py::FunctionalTest::test_bucket_modification kinto_http/tests/functional.py::FunctionalTest::test_bucket_retrieval kinto_http/tests/functional.py::FunctionalTest::test_bucket_retrieval_fails_when_not_created kinto_http/tests/functional.py::FunctionalTest::test_bucket_save kinto_http/tests/functional.py::FunctionalTest::test_bucket_sharing kinto_http/tests/functional.py::FunctionalTest::test_buckets_deletion kinto_http/tests/functional.py::FunctionalTest::test_buckets_deletion_when_no_buckets_exist kinto_http/tests/functional.py::FunctionalTest::test_buckets_retrieval kinto_http/tests/functional.py::FunctionalTest::test_collection_access_forbidden kinto_http/tests/functional.py::FunctionalTest::test_collection_creation kinto_http/tests/functional.py::FunctionalTest::test_collection_creation_if_not_exists kinto_http/tests/functional.py::FunctionalTest::test_collection_deletion kinto_http/tests/functional.py::FunctionalTest::test_collection_deletion_can_still_raise_errors kinto_http/tests/functional.py::FunctionalTest::test_collection_deletion_if_exists kinto_http/tests/functional.py::FunctionalTest::test_collection_list kinto_http/tests/functional.py::FunctionalTest::test_collection_not_found kinto_http/tests/functional.py::FunctionalTest::test_collection_sharing kinto_http/tests/functional.py::FunctionalTest::test_collections_deletion kinto_http/tests/functional.py::FunctionalTest::test_collections_deletion_when_no_collections_exist kinto_http/tests/functional.py::FunctionalTest::test_group_creation kinto_http/tests/functional.py::FunctionalTest::test_group_creation_if_bucket_does_not_exist kinto_http/tests/functional.py::FunctionalTest::test_group_creation_if_not_exists kinto_http/tests/functional.py::FunctionalTest::test_group_deletion kinto_http/tests/functional.py::FunctionalTest::test_group_deletion_can_still_raise_errors kinto_http/tests/functional.py::FunctionalTest::test_group_deletion_if_exists kinto_http/tests/functional.py::FunctionalTest::test_group_list kinto_http/tests/functional.py::FunctionalTest::test_group_update kinto_http/tests/functional.py::FunctionalTest::test_groups_deletion kinto_http/tests/functional.py::FunctionalTest::test_groups_deletion_when_no_groups_exist kinto_http/tests/functional.py::FunctionalTest::test_multiple_record_deletion kinto_http/tests/functional.py::FunctionalTest::test_one_record_deletion kinto_http/tests/functional.py::FunctionalTest::test_patch_record_jsonpatch kinto_http/tests/functional.py::FunctionalTest::test_record_creation_and_retrieval kinto_http/tests/functional.py::FunctionalTest::test_record_deletion_if_exists kinto_http/tests/functional.py::FunctionalTest::test_record_sharing kinto_http/tests/functional.py::FunctionalTest::test_records_deletion_when_no_records_exist kinto_http/tests/functional.py::FunctionalTest::test_records_generator_retrieval kinto_http/tests/functional.py::FunctionalTest::test_records_list_retrieval kinto_http/tests/functional.py::FunctionalTest::test_records_paginated_list_retrieval kinto_http/tests/functional.py::FunctionalTest::test_records_timestamp_retrieval kinto_http/tests/functional.py::FunctionalTest::test_replication kinto_http/tests/functional.py::FunctionalTest::test_request_batching kinto_http/tests/functional.py::FunctionalTest::test_single_record_can_overwrite kinto_http/tests/functional.py::FunctionalTest::test_single_record_creation_if_not_exists kinto_http/tests/functional.py::FunctionalTest::test_single_record_doesnt_overwrite kinto_http/tests/functional.py::FunctionalTest::test_single_record_save kinto_http/tests/functional.py::FunctionalTest::test_updating_data_on_a_collection kinto_http/tests/functional.py::FunctionalTest::test_updating_data_on_a_group
kinto_http/tests/test_client.py::ClientTest::test_batch_does_not_raise_exception_if_batch_4xx_errors_are_ignored kinto_http/tests/test_client.py::ClientTest::test_batch_options_are_transmitted kinto_http/tests/test_client.py::ClientTest::test_batch_raises_exception kinto_http/tests/test_client.py::ClientTest::test_batch_raises_exception_if_subrequest_failed_with_code_4xx kinto_http/tests/test_client.py::ClientTest::test_batch_raises_exception_if_subrequest_failed_with_code_5xx kinto_http/tests/test_client.py::ClientTest::test_client_clone_with_auth kinto_http/tests/test_client.py::ClientTest::test_client_clone_with_auth_and_server_url kinto_http/tests/test_client.py::ClientTest::test_client_clone_with_auth_and_server_url_bucket_and_collection kinto_http/tests/test_client.py::ClientTest::test_client_clone_with_existing_session kinto_http/tests/test_client.py::ClientTest::test_client_clone_with_new_bucket_and_collection kinto_http/tests/test_client.py::ClientTest::test_client_clone_with_new_session kinto_http/tests/test_client.py::ClientTest::test_client_clone_with_server_url kinto_http/tests/test_client.py::ClientTest::test_client_is_represented_properly_with_bucket kinto_http/tests/test_client.py::ClientTest::test_client_is_represented_properly_with_bucket_and_collection kinto_http/tests/test_client.py::ClientTest::test_client_is_represented_properly_without_bucket kinto_http/tests/test_client.py::ClientTest::test_client_uses_default_bucket_if_not_specified kinto_http/tests/test_client.py::ClientTest::test_client_uses_passed_bucket_if_specified kinto_http/tests/test_client.py::ClientTest::test_context_manager_works_as_expected kinto_http/tests/test_client.py::ClientTest::test_server_info kinto_http/tests/test_client.py::BucketTest::test_bucket_names_are_slugified kinto_http/tests/test_client.py::BucketTest::test_create_bucket_can_deduce_id_from_data kinto_http/tests/test_client.py::BucketTest::test_delete_bucket_handles_if_match kinto_http/tests/test_client.py::BucketTest::test_delete_bucket_returns_the_contained_data kinto_http/tests/test_client.py::BucketTest::test_delete_is_issued_on_list_deletion kinto_http/tests/test_client.py::BucketTest::test_get_is_issued_on_list_retrieval kinto_http/tests/test_client.py::BucketTest::test_get_is_issued_on_retrieval kinto_http/tests/test_client.py::BucketTest::test_get_or_create_dont_raise_in_case_of_conflict kinto_http/tests/test_client.py::BucketTest::test_get_or_create_raise_in_other_cases kinto_http/tests/test_client.py::BucketTest::test_http_500_raises_an_error kinto_http/tests/test_client.py::BucketTest::test_patch_is_issued_on_patch kinto_http/tests/test_client.py::BucketTest::test_patch_requires_patch_to_be_patch_type kinto_http/tests/test_client.py::BucketTest::test_permissions_are_retrieved kinto_http/tests/test_client.py::BucketTest::test_put_is_issued_on_creation kinto_http/tests/test_client.py::BucketTest::test_put_is_issued_on_update kinto_http/tests/test_client.py::BucketTest::test_unauthorized_raises_a_kinto_exception kinto_http/tests/test_client.py::BucketTest::test_unexisting_bucket_raises kinto_http/tests/test_client.py::BucketTest::test_update_bucket_can_deduce_id_from_data kinto_http/tests/test_client.py::BucketTest::test_update_bucket_handles_if_match kinto_http/tests/test_client.py::GroupTest::test_create_group_can_deduce_id_from_data kinto_http/tests/test_client.py::GroupTest::test_create_group_raises_if_group_id_is_missing kinto_http/tests/test_client.py::GroupTest::test_patch_group_makes_request kinto_http/tests/test_client.py::GroupTest::test_patch_requires_patch_to_be_patch_type kinto_http/tests/test_client.py::GroupTest::test_update_group_can_deduce_id_from_data kinto_http/tests/test_client.py::GroupTest::test_update_group_raises_if_group_id_is_missing kinto_http/tests/test_client.py::CollectionTest::test_collection_can_be_deleted kinto_http/tests/test_client.py::CollectionTest::test_collection_can_delete_all_its_records kinto_http/tests/test_client.py::CollectionTest::test_collection_creation_issues_an_http_put kinto_http/tests/test_client.py::CollectionTest::test_collection_delete_if_match kinto_http/tests/test_client.py::CollectionTest::test_collection_delete_if_match_not_included_if_not_safe kinto_http/tests/test_client.py::CollectionTest::test_collection_names_are_slugified kinto_http/tests/test_client.py::CollectionTest::test_collection_update_issues_an_http_put kinto_http/tests/test_client.py::CollectionTest::test_collection_update_use_an_if_match_header kinto_http/tests/test_client.py::CollectionTest::test_create_collection_can_deduce_id_from_data kinto_http/tests/test_client.py::CollectionTest::test_create_collection_raises_a_special_error_on_403 kinto_http/tests/test_client.py::CollectionTest::test_data_can_be_sent_on_creation kinto_http/tests/test_client.py::CollectionTest::test_delete_is_issued_on_list_deletion kinto_http/tests/test_client.py::CollectionTest::test_get_collections_returns_the_list_of_collections kinto_http/tests/test_client.py::CollectionTest::test_get_or_create_doesnt_raise_in_case_of_conflict kinto_http/tests/test_client.py::CollectionTest::test_get_or_create_raise_in_other_cases kinto_http/tests/test_client.py::CollectionTest::test_patch_collection_handles_if_match kinto_http/tests/test_client.py::CollectionTest::test_patch_collection_issues_an_http_patch kinto_http/tests/test_client.py::CollectionTest::test_patch_requires_patch_to_be_patch_type kinto_http/tests/test_client.py::CollectionTest::test_update_collection_can_deduce_id_from_data kinto_http/tests/test_client.py::CollectionTest::test_update_handles_if_match kinto_http/tests/test_client.py::RecordTest::test_collection_argument_takes_precedence kinto_http/tests/test_client.py::RecordTest::test_collection_can_delete_a_record kinto_http/tests/test_client.py::RecordTest::test_collection_can_retrieve_all_records kinto_http/tests/test_client.py::RecordTest::test_collection_can_retrieve_records_timestamp kinto_http/tests/test_client.py::RecordTest::test_create_record_can_deduce_id_from_data kinto_http/tests/test_client.py::RecordTest::test_create_record_raises_a_special_error_on_403 kinto_http/tests/test_client.py::RecordTest::test_creation_doesnt_add_if_none_match_when_overwrite kinto_http/tests/test_client.py::RecordTest::test_creation_sends_if_none_match_by_default kinto_http/tests/test_client.py::RecordTest::test_data_and_permissions_are_added_on_create kinto_http/tests/test_client.py::RecordTest::test_generated_record_id_is_an_uuid kinto_http/tests/test_client.py::RecordTest::test_get_or_create_doesnt_raise_in_case_of_conflict kinto_http/tests/test_client.py::RecordTest::test_get_or_create_raise_in_other_cases kinto_http/tests/test_client.py::RecordTest::test_pagination_is_followed kinto_http/tests/test_client.py::RecordTest::test_pagination_is_followed_for_number_of_pages kinto_http/tests/test_client.py::RecordTest::test_pagination_is_not_followed_if_limit_is_specified kinto_http/tests/test_client.py::RecordTest::test_pagination_supports_if_none_match kinto_http/tests/test_client.py::RecordTest::test_patch_record_recognizes_patchtype kinto_http/tests/test_client.py::RecordTest::test_patch_record_requires_data_to_be_patch_type kinto_http/tests/test_client.py::RecordTest::test_patch_record_requires_id kinto_http/tests/test_client.py::RecordTest::test_patch_record_understands_jsonpatch kinto_http/tests/test_client.py::RecordTest::test_patch_record_uses_the_patch_method kinto_http/tests/test_client.py::RecordTest::test_record_delete_if_match kinto_http/tests/test_client.py::RecordTest::test_record_delete_if_match_not_included_if_not_safe kinto_http/tests/test_client.py::RecordTest::test_record_id_is_derived_from_data_if_present kinto_http/tests/test_client.py::RecordTest::test_record_id_is_given_after_creation kinto_http/tests/test_client.py::RecordTest::test_record_issues_a_request_on_retrieval kinto_http/tests/test_client.py::RecordTest::test_records_handles_permissions kinto_http/tests/test_client.py::RecordTest::test_records_issues_a_request_on_delete kinto_http/tests/test_client.py::RecordTest::test_records_timestamp_is_cached kinto_http/tests/test_client.py::RecordTest::test_records_timestamp_is_cached_per_collection kinto_http/tests/test_client.py::RecordTest::test_update_record_can_deduce_id_from_data kinto_http/tests/test_client.py::RecordTest::test_update_record_gets_the_id_from_data_if_exists kinto_http/tests/test_client.py::RecordTest::test_update_record_handles_if_match kinto_http/tests/test_client.py::RecordTest::test_update_record_raises_if_no_id_is_given
README.rst CHANGELOG.rst kinto_http/__init__.py
README.rst CHANGELOG.rst kinto_http/__init__.py