Repository: huggingface/huggingface_hub

Issue:
Programmatically pause a Space
Following [server-side implementation](https://github.com/huggingface/moon-landing/pull/5276) (internal link) of a "Pause" feature, let's have it on `huggingface_hub` side :)

Endpoints:
```
POST /api/:repoType(spaces)/:namespace/:repo/restart
POST /api/:repoType(spaces)/:namespace/:repo/pause
```


Discussion:

Resolving patch:
diff --git a/src/huggingface_hub/__init__.py b/src/huggingface_hub/__init__.py
--- a/src/huggingface_hub/__init__.py
+++ b/src/huggingface_hub/__init__.py
@@ -147,9 +147,11 @@
         "merge_pull_request",
         "model_info",
         "move_repo",
+        "pause_space",
         "rename_discussion",
         "repo_type_and_id_from_hf_id",
         "request_space_hardware",
+        "restart_space",
         "set_access_token",
         "space_info",
         "unlike",
@@ -398,9 +400,11 @@ def __dir__():
         merge_pull_request,  # noqa: F401
         model_info,  # noqa: F401
         move_repo,  # noqa: F401
+        pause_space,  # noqa: F401
         rename_discussion,  # noqa: F401
         repo_type_and_id_from_hf_id,  # noqa: F401
         request_space_hardware,  # noqa: F401
+        restart_space,  # noqa: F401
         set_access_token,  # noqa: F401
         space_info,  # noqa: F401
         unlike,  # noqa: F401
diff --git a/src/huggingface_hub/_space_api.py b/src/huggingface_hub/_space_api.py
--- a/src/huggingface_hub/_space_api.py
+++ b/src/huggingface_hub/_space_api.py
@@ -39,6 +39,7 @@ class SpaceStage(str, Enum):
     RUNTIME_ERROR = "RUNTIME_ERROR"
     DELETING = "DELETING"
     STOPPED = "STOPPED"
+    PAUSED = "PAUSED"
 
 
 class SpaceHardware(str, Enum):
@@ -86,3 +87,9 @@ class SpaceRuntime:
     hardware: Optional[SpaceHardware]
     requested_hardware: Optional[SpaceHardware]
     raw: Dict
+
+    def __init__(self, data: Dict) -> None:
+        self.stage = data["stage"]
+        self.hardware = data["hardware"]["current"]
+        self.requested_hardware = data["hardware"]["requested"]
+        self.raw = data
diff --git a/src/huggingface_hub/hf_api.py b/src/huggingface_hub/hf_api.py
--- a/src/huggingface_hub/hf_api.py
+++ b/src/huggingface_hub/hf_api.py
@@ -3835,21 +3835,11 @@ def get_space_runtime(self, repo_id: str, *, token: Optional[str] = None) -> Spa
                 Hugging Face token. Will default to the locally saved token if
                 not provided.
         Returns:
-            `SpaceRuntime`: dataclass containing runtime information about a Space
-             including Space stage and hardware.
+            [`SpaceRuntime`]: Runtime information about a Space including Space stage and hardware.
         """
-        r = requests.get(
-            f"{self.endpoint}/api/spaces/{repo_id}/runtime",
-            headers=self._build_hf_headers(token=token),
-        )
+        r = requests.get(f"{self.endpoint}/api/spaces/{repo_id}/runtime", headers=self._build_hf_headers(token=token))
         hf_raise_for_status(r)
-        data = r.json()
-        return SpaceRuntime(
-            stage=data["stage"],
-            hardware=data["hardware"]["current"],
-            requested_hardware=data["hardware"]["requested"],
-            raw=data,
-        )
+        return SpaceRuntime(r.json())
 
     @validate_hf_hub_args
     def request_space_hardware(self, repo_id: str, hardware: SpaceHardware, *, token: Optional[str] = None) -> None:
@@ -3865,8 +3855,7 @@ def request_space_hardware(self, repo_id: str, hardware: SpaceHardware, *, token
 
         <Tip>
 
-        It is also possible to request hardware directly when creating the Space repo!
-        See [`create_repo`] for details.
+        It is also possible to request hardware directly when creating the Space repo! See [`create_repo`] for details.
 
         </Tip>
         """
@@ -3877,6 +3866,74 @@ def request_space_hardware(self, repo_id: str, hardware: SpaceHardware, *, token
         )
         hf_raise_for_status(r)
 
+    @validate_hf_hub_args
+    def pause_space(self, repo_id: str, *, token: Optional[str] = None) -> SpaceRuntime:
+        """Pause your Space.
+
+        A paused Space stops executing until manually restarted by its owner. This is different from the sleeping
+        state in which free Spaces go after 72h of inactivity. Paused time is not billed to your account, no matter the
+        hardware you've selected. To restart your Space, use [`restart_space`] and go to your Space settings page.
+
+        For more details, please visit [the docs](https://huggingface.co/docs/hub/spaces-gpus#pause).
+
+        Args:
+            repo_id (`str`):
+                ID of the Space to pause. Example: `"Salesforce/BLIP2"`.
+            token (`str`, *optional*):
+                Hugging Face token. Will default to the locally saved token if not provided.
+
+        Returns:
+            [`SpaceRuntime`]: Runtime information about your Space including `stage=PAUSED` and requested hardware.
+
+        Raises:
+            [`~utils.RepositoryNotFoundError`]:
+                If your Space is not found (error 404). Most probably wrong repo_id or your space is private but you
+                are not authenticated.
+            [`~utils.HfHubHTTPError`]:
+                403 Forbidden: only the owner of a Space can pause it. If you want to manage a Space that you don't
+                own, either ask the owner by opening a Discussion or duplicate the Space.
+            [`~utils.BadRequestError`]:
+                If your Space is a static Space. Static Spaces are always running and never billed. If you want to hide
+                a static Space, you can set it to private.
+        """
+        r = requests.post(f"{self.endpoint}/api/spaces/{repo_id}/pause", headers=self._build_hf_headers(token=token))
+        hf_raise_for_status(r)
+        return SpaceRuntime(r.json())
+
+    @validate_hf_hub_args
+    def restart_space(self, repo_id: str, *, token: Optional[str] = None) -> SpaceRuntime:
+        """Restart your Space.
+
+        This is the only way to programmatically restart a Space if you've put it on Pause (see [`pause_space`]). You
+        must be the owner of the Space to restart it. If you are using an upgraded hardware, your account will be
+        billed as soon as the Space is restarted. You can trigger a restart no matter the current state of a Space.
+
+        For more details, please visit [the docs](https://huggingface.co/docs/hub/spaces-gpus#pause).
+
+        Args:
+            repo_id (`str`):
+                ID of the Space to restart. Example: `"Salesforce/BLIP2"`.
+            token (`str`, *optional*):
+                Hugging Face token. Will default to the locally saved token if not provided.
+
+        Returns:
+            [`SpaceRuntime`]: Runtime information about your Space.
+
+        Raises:
+            [`~utils.RepositoryNotFoundError`]:
+                If your Space is not found (error 404). Most probably wrong repo_id or your space is private but you
+                are not authenticated.
+            [`~utils.HfHubHTTPError`]:
+                403 Forbidden: only the owner of a Space can restart it. If you want to restart a Space that you don't
+                own, either ask the owner by opening a Discussion or duplicate the Space.
+            [`~utils.BadRequestError`]:
+                If your Space is a static Space. Static Spaces are always running and never billed. If you want to hide
+                a static Space, you can set it to private.
+        """
+        r = requests.post(f"{self.endpoint}/api/spaces/{repo_id}/restart", headers=self._build_hf_headers(token=token))
+        hf_raise_for_status(r)
+        return SpaceRuntime(r.json())
+
     def _build_hf_headers(
         self,
         token: Optional[Union[bool, str]] = None,
@@ -4014,3 +4071,5 @@ def _parse_revision_from_pr_url(pr_url: str) -> str:
 delete_space_secret = api.delete_space_secret
 get_space_runtime = api.get_space_runtime
 request_space_hardware = api.request_space_hardware
+pause_space = api.pause_space
+restart_space = api.restart_space


Test patch:
diff --git a/tests/test_hf_api.py b/tests/test_hf_api.py
--- a/tests/test_hf_api.py
+++ b/tests/test_hf_api.py
@@ -2259,6 +2259,13 @@ def test_space_runtime(self) -> None:
         # Raw response from Hub
         self.assertIsInstance(runtime.raw, dict)
 
+    def test_pause_and_restart_space(self) -> None:
+        runtime_after_pause = self.api.pause_space(self.repo_id)
+        self.assertEqual(runtime_after_pause.stage, SpaceStage.PAUSED)
+
+        runtime_after_restart = self.api.restart_space(self.repo_id)
+        self.assertIn(runtime_after_restart.stage, (SpaceStage.BUILDING, SpaceStage.RUNNING_BUILDING))
+
 
 class TestSpaceAPIMocked(unittest.TestCase):
     """