diff --git a/swh/web/client/cli.py b/swh/web/client/cli.py
index 097c51321689fd16acfdf627e9ac7839e58b578c..dc6d739d5bd8796efd0dbd7cc8fd761fd14f3030 100644
--- a/swh/web/client/cli.py
+++ b/swh/web/client/cli.py
@@ -3,7 +3,8 @@
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
-from typing import List
+import os
+from typing import Any, Dict, List
 
 # WARNING: do not import unnecessary things here to keep cli startup time under
 # control
@@ -14,17 +15,58 @@ from swh.core.cli import swh as swh_cli_group
 
 CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
 
+# TODO (T1410): All generic config code should reside in swh.core.config
+DEFAULT_CONFIG_PATH = os.environ.get(
+    "SWH_CONFIG_FILE", os.path.join(click.get_app_dir("swh"), "global.yml")
+)
+
+DEFAULT_CONFIG: Dict[str, Any] = {
+    "api_url": "https://archive.softwareheritage.org/api/1",
+    "bearer_token": None,
+}
+
 
 @swh_cli_group.group(name="web", context_settings=CONTEXT_SETTINGS)
+@click.option(
+    "-C",
+    "--config-file",
+    default=None,
+    type=click.Path(exists=True, dir_okay=False, path_type=str),
+    help=f"Configuration file (default: {DEFAULT_CONFIG_PATH})",
+)
 @click.pass_context
-def web(ctx: Context):
+def web(ctx: Context, config_file: str):
     """Software Heritage web client"""
 
+    import logging
+
+    from swh.core import config
     from swh.web.client.client import WebAPIClient
 
+    if not config_file:
+        config_file = DEFAULT_CONFIG_PATH
+
+    try:
+        conf = config.read_raw_config(config.config_basepath(config_file))
+        if not conf:
+            raise ValueError(f"Cannot parse configuration file: {config_file}")
+
+        if config_file == DEFAULT_CONFIG_PATH:
+            try:
+                conf = conf["swh"]["web"]["client"]
+            except KeyError:
+                pass
+
+        # recursive merge not done by config.read
+        conf = config.merge_configs(DEFAULT_CONFIG, conf)
+    except Exception:
+        logging.warning(
+            "Using default configuration (cannot load custom one)", exc_info=True
+        )
+        conf = DEFAULT_CONFIG
+
     ctx.ensure_object(dict)
-    # TODO (T2872): add configuration file for the web client
-    ctx.obj["client"] = WebAPIClient()
+    ctx.obj["client"] = WebAPIClient(conf["api_url"], conf["bearer_token"])
 
 
 @web.command(name="search")
diff --git a/swh/web/client/client.py b/swh/web/client/client.py
index 483f450f984f372ffec2804b5fa66f043c8aa066..881aa269fe2d4cec7e91b0fb0de2ff431c360721 100644
--- a/swh/web/client/client.py
+++ b/swh/web/client/client.py
@@ -44,6 +44,7 @@ from swh.model.identifiers import (
     SWHID,
     parse_swhid,
 )
+from swh.web.client.cli import DEFAULT_CONFIG
 
 SWHIDish = Union[SWHID, str]
 
@@ -134,16 +135,15 @@ class WebAPIClient:
 
     def __init__(
         self,
-        api_url: str = "https://archive.softwareheritage.org/api/1",
-        bearer_token: Optional[str] = None,
+        api_url: str = DEFAULT_CONFIG["api_url"],
+        bearer_token: Optional[str] = DEFAULT_CONFIG["bearer_token"],
     ):
         """Create a client for the Software Heritage Web API
 
         See: https://archive.softwareheritage.org/api/
 
         Args:
-            api_url: base URL for API calls (default:
-                "https://archive.softwareheritage.org/api/1")
+            api_url: base URL for API calls
             bearer_token: optional bearer token to do authenticated API calls
         """
         api_url = api_url.rstrip("/")