coveralls: 
[![Coverage Status](https://coveralls.io/builds/14970855/badge)](https://coveralls.io/builds/14970855)

Coverage increased (+0.1%) to 90.765% when pulling **1087375ff50f2a8db607c880a465723b8f58d458 on rate-limit-wait** into **28b1c6928d086f8a4d2322a207ac99faf19a6839 on master**.

diff --git a/README.rst b/README.rst
index 7578d2f..fa28ba4 100644
--- a/README.rst
+++ b/README.rst
@@ -73,6 +73,8 @@ Changelog
 dev
 ```
 
+* Add ``minimum_time`` argument to ``SlackAPI.iter`` in order to force a minimum elapsed time between two call to the API
+
 0.3.2
 `````
 
diff --git a/slack/io/abc.py b/slack/io/abc.py
index 4a5ea84..7332345 100644
--- a/slack/io/abc.py
+++ b/slack/io/abc.py
@@ -80,7 +80,7 @@ class SlackAPI(abc.ABC):
                                                     global_headers=self._headers, token=self._token)
         return await self._make_query(url, body, headers)
 
-    async def iter(self, url, data=None, headers=None, *, limit=200, iterkey=None, itermode=None):
+    async def iter(self, url, data=None, headers=None, *, limit=200, iterkey=None, itermode=None, minimum_time=None):
         """
         Iterate over a slack API method supporting pagination
 
@@ -91,6 +91,8 @@ class SlackAPI(abc.ABC):
             limit: Maximum number of results to return per call.
             iterkey: Key in response data to iterate over (required for url string).
             itermode: Iteration mode (required for url string) (one of `cursor`, `page` or `timeline`)
+            minimum_time: Minimum elapsed time (in seconds) between two calls to the Slack API (default to 0).
+             If not reached the client will sleep for the remaining time.
 
         Returns:
             Async iterator over `response_data[key]`
@@ -101,9 +103,15 @@ class SlackAPI(abc.ABC):
         if not data:
             data = {}
 
+        last_request_time = None
         while True:
+            current_time = time.time()
+            if minimum_time and last_request_time and last_request_time + minimum_time > current_time:
+                await self.sleep(last_request_time + minimum_time - current_time)
+
             data, iterkey, itermode = sansio.prepare_iter_request(url, data, iterkey=iterkey, itermode=itermode,
                                                                   limit=limit, itervalue=itervalue)
+            last_request_time = time.time()
             response_data = await self.query(url, data, headers)
             itervalue = sansio.decode_iter_request(response_data)
             for item in response_data[iterkey]:
diff --git a/slack/io/requests.py b/slack/io/requests.py
index 5e0a87a..c384053 100644
--- a/slack/io/requests.py
+++ b/slack/io/requests.py
@@ -76,7 +76,7 @@ class SlackAPI(abc.SlackAPI):
                                                     global_headers=self._headers, token=self._token)
         return self._make_query(url, body, headers)
 
-    def iter(self, url, data=None, headers=None, *, limit=200, iterkey=None, itermode=None):
+    def iter(self, url, data=None, headers=None, *, limit=200, iterkey=None, itermode=None, minimum_time=None):
         """
         Iterate over a slack API method supporting pagination
 
@@ -87,6 +87,8 @@ class SlackAPI(abc.SlackAPI):
             limit: Maximum number of results to return per call.
             iterkey: Key in response data to iterate over (required for url string).
             itermode: Iteration mode (required for url string) (one of `cursor`, `page` or `timeline`)
+            minimum_time: Minimum elapsed time (in seconds) between two calls to the Slack API (default to 0).
+             If not reached the client will sleep for the remaining time.
 
         Returns:
             Async iterator over `response_data[key]`
@@ -97,9 +99,15 @@ class SlackAPI(abc.SlackAPI):
         if not data:
             data = {}
 
+        last_request_time = None
         while True:
+            current_time = time.time()
+            if minimum_time and last_request_time and last_request_time + minimum_time > current_time:
+                self.sleep(last_request_time + minimum_time - current_time)
+
             data, iterkey, itermode = sansio.prepare_iter_request(url, data, iterkey=iterkey, itermode=itermode,
                                                                   limit=limit, itervalue=itervalue)
+            last_request_time = time.time()
             response_data = self.query(url, data, headers)
             itervalue = sansio.decode_iter_request(response_data)
             for item in response_data[iterkey]:

Rate limit
Slack has a rate limit of 1 query / second. When using the iteration mode for paginated request it's easy to get rate limited.

We should add a way to ensure x seconds have passed between multiple api call using the iteration mode.
diff --git a/tests/test_io.py b/tests/test_io.py
index 6ff909d..fc01c8c 100644
--- a/tests/test_io.py
+++ b/tests/test_io.py
@@ -1,9 +1,11 @@
 import json
 import asks
 import trio
+import time
 import slack
 import curio
 import pytest
+import asyncio
 import aiohttp
 import requests
 import datetime
@@ -166,6 +168,40 @@ class TestABC:
             async for _ in client.iter('https://slack.com/api/channels.list'):  # noQa: F841
                 pass
 
+    @pytest.mark.parametrize('client', ({'retry_when_rate_limit': False,
+                                         '_request': [
+                                             {'body': 'channels_iter'},
+                                             {'body': 'channels'}
+                                         ]}, ), indirect=True)
+    async def test_iter_wait(self, client):
+        client.sleep = asynctest.CoroutineMock()
+
+        channels = 0
+        async for _ in client.iter(methods.CHANNELS_LIST, minimum_time=2):  # noQa: F841
+            channels += 1
+
+        assert channels == 4
+        assert client._request.call_count == 2
+        assert client.sleep.call_count == 1
+        assert 2 > client.sleep.call_args[0][0] > 1.9
+
+    @pytest.mark.parametrize('client', ({'retry_when_rate_limit': False,
+                                         '_request': [
+                                             {'body': 'channels_iter'},
+                                             {'body': 'channels'}
+                                         ]}, ), indirect=True)
+    async def test_iter_no_wait(self, client):
+        client.sleep = asynctest.CoroutineMock()
+
+        channels = 0
+        async for _ in client.iter(methods.CHANNELS_LIST, minimum_time=1):  # noQa: F841
+            channels += 1
+            await asyncio.sleep(0.5)
+
+        assert channels == 4
+        assert client._request.call_count == 2
+        assert client.sleep.call_count == 0
+
     @pytest.mark.parametrize('client', ({'_request': [{'body': 'auth.test'}, {'body': 'users.info'}]}, ), indirect=True)
     async def test_find_bot_id(self, client):
         bot_id = await client._find_bot_id()
@@ -314,6 +350,40 @@ class TestNoAsync:
             'POST', 'https://slack.com/api/channels.list', {}, {'limit': 200, 'token': token, 'cursor': itercursor}
         )
 
+    @pytest.mark.parametrize('client', ({'retry_when_rate_limit': False,
+                                         '_request': [
+                                             {'body': 'channels_iter'},
+                                             {'body': 'channels'}
+                                         ]}, ), indirect=True)
+    def test_iter_wait(self, client):
+        client.sleep = asynctest.CoroutineMock()
+
+        channels = 0
+        for _ in client.iter(methods.CHANNELS_LIST, minimum_time=2):  # noQa: F841
+            channels += 1
+
+        assert channels == 4
+        assert client._request.call_count == 2
+        assert client.sleep.call_count == 1
+        assert 2 > client.sleep.call_args[0][0] > 1.9
+
+    @pytest.mark.parametrize('client', ({'retry_when_rate_limit': False,
+                                         '_request': [
+                                             {'body': 'channels_iter'},
+                                             {'body': 'channels'}
+                                         ]}, ), indirect=True)
+    def test_iter_no_wait(self, client):
+        client.sleep = asynctest.CoroutineMock()
+
+        channels = 0
+        for _ in client.iter(methods.CHANNELS_LIST, minimum_time=1):  # noQa: F841
+            channels += 1
+            time.sleep(0.5)
+
+        assert channels == 4
+        assert client._request.call_count == 2
+        assert client.sleep.call_count == 0
+
     @pytest.mark.parametrize('client', ({'_request': [{'body': 'auth.test'}, {'body': 'users.info'}]}, ), indirect=True)
     def test_find_bot_id(self, client):
         bot_id = client._find_bot_id()

pip install -e .[dev]
parse_log_pytest
pytest
pytest
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
aiohappyeyeballs==2.6.1
aiohttp==3.11.14
aiosignal==1.3.2
alabaster==0.7.16
anyio==3.7.1
asks==3.0.0
async-generator==1.10
async-timeout==5.0.1
asynctest==0.13.0
attrs==25.3.0
Automat==24.8.1
babel==2.17.0
cachetools==5.5.2
certifi==2025.1.31
cffi==1.17.1
chardet==5.2.0
charset-normalizer==3.4.1
colorama==0.4.6
constantly==23.10.4
coverage==7.8.0
cryptography==44.0.2
curio==1.6
distlib==0.3.9
docutils==0.21.2
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
filelock==3.18.0
flake8==7.2.0
frozenlist==1.5.0
h11==0.14.0
hyperlink==21.0.0
idna==3.10
imagesize==1.4.1
importlib_metadata==8.6.1
incremental==24.7.2
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
Jinja2==3.1.6
MarkupSafe==3.0.2
mccabe==0.7.0
multidict==6.2.0
outcome==1.3.0.post0
packaging @ file:///croot/packaging_1734472117206/work
platformdirs==4.3.7
pluggy @ file:///croot/pluggy_1733169602837/work
pockets==0.9.1
propcache==0.3.1
pyasn1==0.6.1
pyasn1_modules==0.4.2
pycodestyle==2.13.0
pycparser==2.22
pyflakes==3.3.1
Pygments==2.19.1
pyOpenSSL==25.0.0
pyproject-api==1.9.0
pytest @ file:///croot/pytest_1738938843180/work
pytest-asyncio==0.26.0
pytest-cov==6.0.0
pytest-cover==3.0.0
pytest-coverage==0.0
pytest-runner==6.0.1
requests==2.32.3
service-identity==24.2.0
six==1.17.0
-e git+https://github.com/pyslackers/slack-sansio.git@28b1c6928d086f8a4d2322a207ac99faf19a6839#egg=slack_sansio
sniffio==1.3.1
snowballstemmer==2.2.0
sortedcontainers==2.4.0
Sphinx==7.4.7
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-asyncio==0.3.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-napoleon==0.7
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
tomli==2.2.1
tox==4.25.0
treq==24.9.1
trio==0.29.0
Twisted==24.11.0
typing_extensions==4.13.0
urllib3==2.3.0
virtualenv==20.29.3
websocket-client==1.8.0
yarl==1.18.3
zipp==3.21.0
zope.interface==7.2

name: slack-sansio
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
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_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
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=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
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - aiohappyeyeballs==2.6.1
      - aiohttp==3.11.14
      - aiosignal==1.3.2
      - alabaster==0.7.16
      - anyio==3.7.1
      - asks==3.0.0
      - async-generator==1.10
      - async-timeout==5.0.1
      - asynctest==0.13.0
      - attrs==25.3.0
      - automat==24.8.1
      - babel==2.17.0
      - cachetools==5.5.2
      - certifi==2025.1.31
      - cffi==1.17.1
      - chardet==5.2.0
      - charset-normalizer==3.4.1
      - colorama==0.4.6
      - constantly==23.10.4
      - coverage==7.8.0
      - cryptography==44.0.2
      - curio==1.6
      - distlib==0.3.9
      - docutils==0.21.2
      - filelock==3.18.0
      - flake8==7.2.0
      - frozenlist==1.5.0
      - h11==0.14.0
      - hyperlink==21.0.0
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - incremental==24.7.2
      - jinja2==3.1.6
      - markupsafe==3.0.2
      - mccabe==0.7.0
      - multidict==6.2.0
      - outcome==1.3.0.post0
      - platformdirs==4.3.7
      - pockets==0.9.1
      - propcache==0.3.1
      - pyasn1==0.6.1
      - pyasn1-modules==0.4.2
      - pycodestyle==2.13.0
      - pycparser==2.22
      - pyflakes==3.3.1
      - pygments==2.19.1
      - pyopenssl==25.0.0
      - pyproject-api==1.9.0
      - pytest-asyncio==0.26.0
      - pytest-cov==6.0.0
      - pytest-cover==3.0.0
      - pytest-coverage==0.0
      - pytest-runner==6.0.1
      - requests==2.32.3
      - service-identity==24.2.0
      - six==1.17.0
      - sniffio==1.3.1
      - snowballstemmer==2.2.0
      - sortedcontainers==2.4.0
      - sphinx==7.4.7
      - sphinxcontrib-applehelp==2.0.0
      - sphinxcontrib-asyncio==0.3.0
      - sphinxcontrib-devhelp==2.0.0
      - sphinxcontrib-htmlhelp==2.1.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-napoleon==0.7
      - sphinxcontrib-qthelp==2.0.0
      - sphinxcontrib-serializinghtml==2.0.0
      - tomli==2.2.1
      - tox==4.25.0
      - treq==24.9.1
      - trio==0.29.0
      - twisted==24.11.0
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - virtualenv==20.29.3
      - websocket-client==1.8.0
      - yarl==1.18.3
      - zipp==3.21.0
      - zope-interface==7.2
prefix: /opt/conda/envs/slack-sansio

tests/test_io.py::TestABC::test_iter_wait[FakeIO-client0] tests/test_io.py::TestABC::test_iter_no_wait[FakeIO-client0] tests/test_io.py::TestNoAsync::test_iter_wait[client0-SlackAPI] tests/test_io.py::TestNoAsync::test_iter_no_wait[client0-SlackAPI]
tests/test_io.py::TestTrio::test__request tests/test_io.py::TestCurio::test__request
tests/test_io.py::TestABC::test_query[client0-FakeIO] tests/test_io.py::TestABC::test_query[client1-FakeIO] tests/test_io.py::TestABC::test_query_url[client0-FakeIO] tests/test_io.py::TestABC::test_query_url[client1-FakeIO] tests/test_io.py::TestABC::test_query_long_url[client0-FakeIO] tests/test_io.py::TestABC::test_query_long_url[client1-FakeIO] tests/test_io.py::TestABC::test_query_webhook_url[client0-FakeIO] tests/test_io.py::TestABC::test_query_webhook_url[client1-FakeIO] tests/test_io.py::TestABC::test_query_data[client0-FakeIO] tests/test_io.py::TestABC::test_query_data[client1-FakeIO] tests/test_io.py::TestABC::test_query_data_webhook[client0-FakeIO] tests/test_io.py::TestABC::test_query_data_webhook[client1-FakeIO] tests/test_io.py::TestABC::test_query_headers[client0-FakeIO] tests/test_io.py::TestABC::test_query_headers[client1-FakeIO] tests/test_io.py::TestABC::test_retry_rate_limited[FakeIO-client0] tests/test_io.py::TestABC::test_retry_rate_limited_with_body[FakeIO-client0] tests/test_io.py::TestABC::test_retry_rate_limited_with_headers[FakeIO-client0] tests/test_io.py::TestABC::test_raise_rate_limited[FakeIO-client0] tests/test_io.py::TestABC::test_iter[FakeIO-client0] tests/test_io.py::TestABC::test_iter_itermode_iterkey[FakeIO-client0] tests/test_io.py::TestABC::test_iter_not_supported[client0-FakeIO] tests/test_io.py::TestABC::test_iter_not_supported[client1-FakeIO] tests/test_io.py::TestABC::test_find_bot_id[FakeIO-client0] tests/test_io.py::TestABC::test_find_rtm_url[FakeIO-client0] tests/test_io.py::TestABC::test_incoming_rtm[client0-rtm_iterator0-FakeIO] tests/test_io.py::TestABC::test_incoming_rtm[client1-rtm_iterator0-FakeIO] tests/test_io.py::TestABC::test_incoming_rtm_reconnect[client0-FakeIO-rtm_iterator0] tests/test_io.py::TestABC::test_incoming_rtm_reconnect[client1-FakeIO-rtm_iterator0] tests/test_io.py::TestABC::test_incoming_rtm_discard_bot_id[client0-FakeIO-rtm_iterator0] tests/test_io.py::TestABC::test_incoming_rtm_discard_bot_id[client1-FakeIO-rtm_iterator0] tests/test_io.py::TestABC::test_incoming_rtm_skip[client0-FakeIO-rtm_iterator0] tests/test_io.py::TestABC::test_incoming_rtm_skip[client1-FakeIO-rtm_iterator0] tests/test_io.py::TestNoAsync::test_query[client0-SlackAPI] tests/test_io.py::TestNoAsync::test_query[client1-SlackAPI] tests/test_io.py::TestNoAsync::test_query_data[client0-SlackAPI] tests/test_io.py::TestNoAsync::test_query_data[client1-SlackAPI] tests/test_io.py::TestNoAsync::test_query_headers[client0-SlackAPI] tests/test_io.py::TestNoAsync::test_query_headers[client1-SlackAPI] tests/test_io.py::TestNoAsync::test_retry_rate_limited[client0-SlackAPI] tests/test_io.py::TestNoAsync::test_retry_rate_limited_with_body[client0-SlackAPI] tests/test_io.py::TestNoAsync::test_retry_rate_limited_with_headers[client0-SlackAPI] tests/test_io.py::TestNoAsync::test_raise_rate_limited[client0-SlackAPI] tests/test_io.py::TestNoAsync::test_iter[client0-SlackAPI] tests/test_io.py::TestNoAsync::test_find_bot_id[client0-SlackAPI] tests/test_io.py::TestNoAsync::test_find_rtm_url[client0-SlackAPI] tests/test_io.py::TestNoAsync::test_incoming_rtm[client0-rtm_iterator_non_async0-SlackAPI] tests/test_io.py::TestNoAsync::test_incoming_rtm[client1-rtm_iterator_non_async0-SlackAPI] tests/test_io.py::TestNoAsync::test_incoming_rtm_reconnect[client0-rtm_iterator_non_async0-SlackAPI] tests/test_io.py::TestNoAsync::test_incoming_rtm_reconnect[client1-rtm_iterator_non_async0-SlackAPI] tests/test_io.py::TestNoAsync::test_incoming_rtm_discard_bot_id[client0-rtm_iterator_non_async0-SlackAPI] tests/test_io.py::TestNoAsync::test_incoming_rtm_discard_bot_id[client1-rtm_iterator_non_async0-SlackAPI] tests/test_io.py::TestNoAsync::test_incoming_rtm_skip[client0-rtm_iterator_non_async0-SlackAPI] tests/test_io.py::TestNoAsync::test_incoming_rtm_skip[client1-rtm_iterator_non_async0-SlackAPI] tests/test_io.py::TestRequest::test_sleep tests/test_io.py::TestRequest::test__request tests/test_io.py::TestAiohttp::test_sleep tests/test_io.py::TestAiohttp::test__request tests/test_io.py::TestTrio::test_sleep tests/test_io.py::TestCurio::test_sleep
README.rst slack/io/abc.py slack/io/requests.py
README.rst slack/io/abc.py slack/io/requests.py