diff --git a/src/friendlypins/board.py b/src/friendlypins/board.py
index 045655f..7f51a3b 100644
--- a/src/friendlypins/board.py
+++ b/src/friendlypins/board.py
@@ -64,16 +64,12 @@ class Board(object):
         return int(self._data['counts']['pins'])
 
     @property
-    def all_pins(self):
-        """Gets a list of all pins from this board
+    def pins(self):
+        """Generator for iterating over the pins linked to this board
 
-        NOTE: This process may take a long time to complete and require
-        a lot of memory for boards that contain large numbers of pins
-
-        :rtype: :class:`list` of :class:`friendlypins.pin.Pin`
+        :rtype: Generator of :class:`friendlypins.pin.Pin`
         """
-        self._log.debug('Gettings all pins for board %s...', self.name)
-        retval = list()
+        self._log.debug('Loading pins for board %s...', self.name)
 
         properties = {
             "fields": ','.join([
@@ -93,19 +89,20 @@ class Board(object):
             ])
         }
 
+        page = 0
         while True:
+            self._log.debug("Loading pins page %s", page)
             result = self._io.get(
                 "boards/{0}/pins".format(self.unique_id),
                 properties)
             assert 'data' in result
 
             for cur_item in result['data']:
-                retval.append(Pin(cur_item, self._io))
+                yield Pin(cur_item, self._io)
             if not result["page"]["cursor"]:
                 break
             properties["cursor"] = result["page"]["cursor"]
-
-        return retval
+            page += 1
 
 
 if __name__ == "__main__":
diff --git a/src/friendlypins/user.py b/src/friendlypins/user.py
index de3306d..3d69138 100644
--- a/src/friendlypins/user.py
+++ b/src/friendlypins/user.py
@@ -94,21 +94,39 @@ class User(object):
 
     @property
     def boards(self):
-        """Gets a list of boards owned by this user
+        """Generator for iterating over the boards owned by this user
 
-        :rtype: :class:`list` of :class:`friendlypins.board.Board`
+        :rtype: Generator of :class:`friendlypins.board.Board`
         """
-        self._log.debug("Loading boards for user %s...", self.name)
-
-        fields = "id,name,url,description,creator,created_at,counts,image"
-        result = self._io.get('me/boards', {"fields": fields})
-
-        assert 'data' in result
-
-        retval = []
-        for cur_item in result['data']:
-            retval.append(Board(cur_item, self._io))
-        return retval
+        self._log.debug('Loading boards for user %s...', self.name)
+
+        properties = {
+            "fields": ','.join([
+                "id",
+                "name",
+                "url",
+                "description",
+                "creator",
+                "created_at",
+                "counts",
+                "image"
+            ])
+        }
+
+        page = 0
+        while True:
+            self._log.debug("Loading boards page %s", page)
+            result = self._io.get("me/boards", properties)
+            assert 'data' in result
+
+            for cur_item in result['data']:
+                yield Board(cur_item, self._io)
+
+            if not result["page"]["cursor"]:
+                break
+
+            properties["cursor"] = result["page"]["cursor"]
+            page += 1
 
 
 if __name__ == "__main__":
diff --git a/src/friendlypins/utils/console_actions.py b/src/friendlypins/utils/console_actions.py
index 1ca66ef..da40dfc 100644
--- a/src/friendlypins/utils/console_actions.py
+++ b/src/friendlypins/utils/console_actions.py
@@ -71,13 +71,12 @@ def download_thumbnails(api_token, board_name, output_folder, delete):
         log.error("Could not find selected board: %s", board_name)
         return 1
 
-    all_pins = selected_board.all_pins
     log.info('Downloading thumbnails...')
     if not os.path.exists(output_folder):
         os.makedirs(output_folder)
 
     with tqdm(total=selected_board.num_pins, unit='b', ncols=80) as pbar:
-        for cur_pin in all_pins:
+        for cur_pin in selected_board.pins:
             retval = _download_pin(cur_pin, output_folder)
             if retval:
                 return retval

lazy load boards and pins
To simplify and optimize the interactions with boards and pins we should lazy-load the data from these two API calls using iterators:

    User.boards
    Board.all_pins
diff --git a/unit_tests/test_board.py b/unit_tests/test_board.py
index 5daac85..2314b19 100644
--- a/unit_tests/test_board.py
+++ b/unit_tests/test_board.py
@@ -24,7 +24,7 @@ def test_board_properties():
     assert obj.num_pins == expected_pin_count
 
 
-def test_get_all_pins():
+def test_get_pins():
     data = {
         'id': '987654321',
         'name': 'MyBoard'
@@ -54,7 +54,9 @@ def test_get_all_pins():
     mock_io.get.return_value = expected_data
     obj = Board(data, mock_io)
 
-    result = obj.all_pins
+    result = list()
+    for item in obj.pins:
+        result.append(item)
 
     assert len(result) == 1
     assert expected_url == result[0].url
diff --git a/unit_tests/test_console_actions.py b/unit_tests/test_console_actions.py
index 170f224..92138cc 100644
--- a/unit_tests/test_console_actions.py
+++ b/unit_tests/test_console_actions.py
@@ -267,7 +267,10 @@ def test_download_thumbnails_missing_board(rest_io, action_requests, mock_open,
             "counts": {
                 "pins": 1
             }
-        }]
+        }],
+        "page": {
+            "cursor": None
+        }
     }
 
     # Fake pin data for the fake board, with fake thumbnail metadata
diff --git a/unit_tests/test_user.py b/unit_tests/test_user.py
index 9184835..0512b58 100644
--- a/unit_tests/test_user.py
+++ b/unit_tests/test_user.py
@@ -44,13 +44,19 @@ def test_get_boards():
             "id": str(expected_id),
             "name": expected_name,
             "url": expected_url
-        }]
+        }],
+        "page": {
+            "cursor": None
+        }
     }
 
     mock_io = mock.MagicMock()
     mock_io.get.return_value = expected_data
     obj = User(data, mock_io)
-    result = obj.boards
+
+    result = list()
+    for item in obj.boards:
+        result.append(item)
 
     assert len(result) == 1
     assert expected_url == result[0].url

pip install -e .[dev]
parse_log_pytest
pytest
pytest pytest-cov mock
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
astroid==2.6.6
attrs @ file:///opt/conda/conda-bld/attrs_1642510447205/work
Babel==2.11.0
bleach==4.1.0
cachetools==4.2.4
certifi==2021.5.30
chardet==5.0.0
charset-normalizer==2.0.12
colorama==0.4.5
coverage==6.2
dateutils==0.6.12
distlib==0.3.9
docutils==0.18.1
filelock==3.4.1
-e git+https://github.com/TheFriendlyCoder/friendlypins.git@9c0aa4ebcde5ad444e342aaa2b3315339dbc36f6#egg=friendlypins
idna==3.10
imagesize==1.4.1
importlib-metadata==4.8.3
importlib-resources==5.4.0
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
isort==5.10.1
Jinja2==3.0.3
lazy-object-proxy==1.7.1
mando==0.7.1
MarkupSafe==2.0.1
mccabe==0.6.1
mock==5.2.0
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
Pillow==8.4.0
pkginfo==1.10.0
platformdirs==2.4.0
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
Pygments==2.14.0
pylint==3.0.0a4
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pytest==6.2.4
pytest-cov==4.0.0
python-dateutil==2.9.0.post0
pytz==2025.2
radon==6.0.1
readme-renderer==34.0
requests==2.27.1
requests-toolbelt==1.0.0
six==1.17.0
snowballstemmer==2.2.0
Sphinx==5.3.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
tomli==1.2.3
tox==4.0.0a9
tqdm==4.64.1
twine==1.15.0
typed-ast==1.4.3
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
urllib3==1.26.20
virtualenv==20.17.1
webencodings==0.5.1
wrapt==1.12.1
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work

name: friendlypins
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
  - attrs=21.4.0=pyhd3eb1b0_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - importlib_metadata=4.8.1=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - more-itertools=8.12.0=pyhd3eb1b0_0
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=21.3=pyhd3eb1b0_0
  - pip=21.2.2=py36h06a4308_0
  - pluggy=0.13.1=py36h06a4308_0
  - py=1.11.0=pyhd3eb1b0_0
  - pyparsing=3.0.4=pyhd3eb1b0_0
  - pytest=6.2.4=py36h06a4308_2
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - typing_extensions=4.1.1=pyh06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.6.0=pyhd3eb1b0_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - astroid==2.6.6
      - babel==2.11.0
      - bleach==4.1.0
      - cachetools==4.2.4
      - chardet==5.0.0
      - charset-normalizer==2.0.12
      - colorama==0.4.5
      - coverage==6.2
      - dateutils==0.6.12
      - distlib==0.3.9
      - docutils==0.18.1
      - filelock==3.4.1
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==4.8.3
      - importlib-resources==5.4.0
      - isort==5.10.1
      - jinja2==3.0.3
      - lazy-object-proxy==1.7.1
      - mando==0.7.1
      - markupsafe==2.0.1
      - mccabe==0.6.1
      - mock==5.2.0
      - pillow==8.4.0
      - pkginfo==1.10.0
      - platformdirs==2.4.0
      - pygments==2.14.0
      - pylint==3.0.0a4
      - pytest-cov==4.0.0
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - radon==6.0.1
      - readme-renderer==34.0
      - requests==2.27.1
      - requests-toolbelt==1.0.0
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==5.3.0
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - tomli==1.2.3
      - tox==4.0.0a9
      - tqdm==4.64.1
      - twine==1.15.0
      - typed-ast==1.4.3
      - urllib3==1.26.20
      - virtualenv==20.17.1
      - webencodings==0.5.1
      - wrapt==1.12.1
prefix: /opt/conda/envs/friendlypins

unit_tests/test_board.py::test_get_pins
unit_tests/test_board.py::test_board_properties unit_tests/test_console_actions.py::test_download_thumbnails unit_tests/test_console_actions.py::test_download_thumbnails_with_delete unit_tests/test_console_actions.py::test_download_thumbnails_error unit_tests/test_console_actions.py::test_download_thumbnails_missing_board unit_tests/test_console_actions.py::test_download_thumbnails_exists unit_tests/test_user.py::test_user_properties unit_tests/test_user.py::test_get_boards
src/friendlypins/board.py src/friendlypins/user.py src/friendlypins/utils/console_actions.py
src/friendlypins/board.py src/friendlypins/user.py src/friendlypins/utils/console_actions.py