<!--⚠️ Note that this file is in Markdown but contains specific syntax for our doc-builder (similar to MDX) that may not be
rendered properly in your Markdown viewer.
-->

# Manage your Space

In this guide, we will see how to manage your Space runtime
([secrets](https://huggingface.co/docs/hub/spaces-overview#managing-secrets),
[hardware](https://huggingface.co/docs/hub/spaces-gpus), and volumes) using `huggingface_hub`.

## Search for Spaces

You can search for Spaces on the Hub using semantic search with [`search_spaces`]. This uses embedding-based search for multi-word queries and full-text search for single-word queries.

```py
>>> from huggingface_hub import search_spaces
>>> results = list(search_spaces("generate image"))
>>> results[0]
SpaceSearchResult(id='mrfakename/Z-Image-Turbo', title='Z Image Turbo', sdk='gradio', likes=2867, ...)
```

You can filter results by SDK or tags:

```py
>>> results = search_spaces("chatbot", sdk="gradio", filter="mcp-server")
```

Or via CLI:

```bash
>>> hf spaces search "generate image"
>>> hf spaces search "chatbot" --sdk gradio --limit 5
```

## A simple example: configure secrets and hardware.

Here is an end-to-end example to create and set up a Space on the Hub.

### Create a Space on the Hub

```py
>>> from huggingface_hub import HfApi
>>> repo_id = "Wauplin/my-cool-training-space"
>>> api = HfApi()

# For example with a Gradio SDK
>>> api.create_repo(repo_id=repo_id, repo_type="space", space_sdk="gradio")
```

### Create a Space from a template

Instead of starting from an empty Space, you can seed a new Space from one of the official templates offered on the Hub (e.g. JupyterLab, a Gradio chatbot, a Streamlit app, etc.). List the available templates with [`list_space_templates`], then pass a template's `repo_id` (or its short `name`) as `space_template` to [`create_repo`]. Note that `space_sdk` is still required: the template seeds the files while the SDK sets the card metadata.

```py
>>> from huggingface_hub import HfApi
>>> api = HfApi()

# List available templates
>>> for template in api.list_space_templates():
...     print(f"{template.name} ({template.repo_id})")
Streamlit (streamlit/streamlit-template-space)
JupyterLab (SpacesExamples/jupyterlab)
chatbot (gradio-templates/chatbot)
...

# Create a Space from a template, by repo id or by short name
>>> api.create_repo(repo_id=repo_id, repo_type="space", space_template="SpacesExamples/jupyterlab")
>>> api.create_repo(repo_id=repo_id, repo_type="space", space_template="JupyterLab")
```

Some templates are recommended to be private (e.g. JupyterLab). If you don't explicitly set visibility, such Spaces are created as private automatically.

From the CLI, the same is available through `hf spaces templates` and `hf repos create --template`:

```bash
# List available templates
>>> hf spaces templates
NAME            REPO_ID                                        SDK    PREFERRED_PRIVATE
--------------- ---------------------------------------------- ------ -----------------
Streamlit       streamlit/streamlit-template-space             docker
JupyterLab      SpacesExamples/jupyterlab                      docker ✔
Argilla         argilla/argilla-template-space                 docker
Livebook        livebook-dev/livebook                          docker
...

# Create a Space from a template
>>> hf repos create my-jupyterlab --type space --template jupyterlab
```

### Duplicate a Space

This can prove useful if you want to build up from an existing Space instead of starting from scratch.
It is also useful is you want control over the configuration/settings of a public Space. See [`duplicate_repo`] for more details.

```py
>>> api.duplicate_repo("multimodalart/dreambooth-training", repo_type="space")
```

### Upload your code using your preferred solution

Here is an example to upload the local folder `src/` from your machine to your Space:

```py
>>> api.upload_folder(repo_id=repo_id, repo_type="space", folder_path="src/")
```

At this step, your app should already be running on the Hub for free !
However, you might want to configure it further with secrets and upgraded hardware.

### Configure secrets and variables

Your Space might require some secret keys, token or variables to work.
See [docs](https://huggingface.co/docs/hub/spaces-overview#managing-secrets) for more details.
For example, an HF token to upload an image dataset to the Hub once generated from your Space.

```py
>>> api.add_space_secret(repo_id=repo_id, key="HF_TOKEN", value="hf_api_***")
>>> api.add_space_variable(repo_id=repo_id, key="MODEL_REPO_ID", value="user/repo")
```

You can list existing secrets and variables. Secret values are write-only, so only keys, descriptions, and update timestamps are returned:

```py
>>> api.get_space_secrets(repo_id=repo_id)
{'HF_TOKEN': SpaceSecret(key='HF_TOKEN', description=None, updated_at=datetime.datetime(...))}
>>> api.get_space_variables(repo_id=repo_id)
{'MODEL_REPO_ID': SpaceVariable(key='MODEL_REPO_ID', value='user/repo', description=None, updated_at=...)}
```

Secrets and variables can be deleted as well:
```py
>>> api.delete_space_secret(repo_id=repo_id, key="HF_TOKEN")
>>> api.delete_space_variable(repo_id=repo_id, key="MODEL_REPO_ID")
```

> [!TIP]
> From within your Space, secrets are available as environment variables (or
> Streamlit Secrets Management if using Streamlit). No need to fetch them via the API!

> [!WARNING]
> Any change in your Space configuration (secrets or hardware) will trigger a restart of your app.

**Bonus: set secrets and variables when creating or duplicating the Space!**

Secrets and variables can be set when creating or duplicating a space:

```py
>>> api.create_repo(
...     repo_id=repo_id,
...     repo_type="space",
...     space_sdk="gradio",
...     space_secrets=[{"key"="HF_TOKEN", "value"="hf_api_***"}, ...],
...     space_variables=[{"key"="MODEL_REPO_ID", "value"="user/repo"}, ...],
... )
```

```py
>>> api.duplicate_repo(
...     from_id=repo_id,
...     repo_type="space",
...     space_secrets=[{"key"="HF_TOKEN", "value"="hf_api_***"}, ...],
...     space_variables=[{"key"="MODEL_REPO_ID", "value"="user/repo"}, ...],
... )
```

### Configure the hardware

By default, your Space will run on a CPU environment for free. You can upgrade the hardware
to run it on GPUs. A payment card or a community grant is required to access upgrade your
Space. See [docs](https://huggingface.co/docs/hub/spaces-gpus) for more details.

```py
# Use `SpaceHardware` enum
>>> from huggingface_hub import SpaceHardware
>>> api.request_space_hardware(repo_id=repo_id, hardware=SpaceHardware.T4_MEDIUM)

# Or simply pass a string value
>>> api.request_space_hardware(repo_id=repo_id, hardware="t4-medium")
```

Hardware updates are not done immediately as your Space has to be reloaded on our servers.
At any time, you can check on which hardware your Space is running to see if your request
has been met.

```py
>>> runtime = api.get_space_runtime(repo_id=repo_id)
>>> runtime.stage
"RUNNING_BUILDING"
>>> runtime.hardware
"cpu-basic"
>>> runtime.requested_hardware
"t4-medium"
```

You now have a Space fully configured. Make sure to downgrade your Space back to "cpu-classic"
when you are done using it.

**Bonus: request hardware when creating or duplicating the Space!**

Upgraded hardware will be automatically assigned to your Space once it's built.

```py
>>> api.create_repo(
...     repo_id=repo_id,
...     repo_type="space",
...     space_sdk="gradio"
...     space_hardware="cpu-upgrade",
...     space_sleep_time="7200", # 2 hours in secs
... )
```
```py
>>> api.duplicate_repo(
...     from_id=repo_id,
...     repo_type="space",
...     space_hardware="cpu-upgrade",
...     space_sleep_time="7200", # 2 hours in secs
... )
```

### Pause and restart your Space

By default if your Space is running on an upgraded hardware, it will never be stopped. However to avoid getting billed,
you might want to pause it when you are not using it. This is possible using [`pause_space`]. A paused Space will be
inactive until the owner of the Space restarts it, either with the UI or via API using [`restart_space`].
For more details about paused mode, please refer to [this section](https://huggingface.co/docs/hub/spaces-gpus#pause)

```py
# Pause your Space to avoid getting billed
>>> api.pause_space(repo_id=repo_id)
# (...)
# Restart it when you need it
>>> api.restart_space(repo_id=repo_id)
```

Another possibility is to set a timeout for your Space. If your Space is inactive for more than the timeout duration,
it will go to sleep. Any visitor landing on your Space will start it back up. You can set a timeout using
[`set_space_sleep_time`]. For more details about sleeping mode, please refer to [this section](https://huggingface.co/docs/hub/spaces-gpus#sleep-time).

```py
# Put your Space to sleep after 1h of inactivity
>>> api.set_space_sleep_time(repo_id=repo_id, sleep_time=3600)
```

Note: if you are using a 'cpu-basic' hardware, you cannot configure a custom sleep time. Your Space will automatically
be paused after 48h of inactivity.

**Bonus: set a sleep time while requesting hardware**

Upgraded hardware will be automatically assigned to your Space once it's built.

```py
>>> api.request_space_hardware(repo_id=repo_id, hardware=SpaceHardware.T4_MEDIUM, sleep_time=3600)
```

**Bonus: set a sleep time when creating or duplicating the Space!**

```py
>>> api.create_repo(
...     repo_id=repo_id,
...     repo_type="space",
...     space_sdk="gradio"
...     space_hardware="t4-medium",
...     space_sleep_time="3600",
... )
```
```py
>>> api.duplicate_repo(
...     from_id=repo_id,
...     repo_type="space",
...     space_hardware="t4-medium",
...     space_sleep_time="3600",
... )
```

### Debug a failing Space by reading its logs

When a Space fails to build or crashes at runtime, the logs you normally view in the browser are also available programmatically via [`fetch_space_logs`]. This is particularly useful from scripts or agentic workflows where opening a browser is not an option.

```py
# Drain the currently available run logs and return immediately (like `docker logs`)
>>> for line in api.fetch_space_logs(repo_id=repo_id):
...     print(line, end="")

# Read the container build logs instead (useful when the Space is stuck in BUILD_ERROR)
>>> for line in api.fetch_space_logs(repo_id=repo_id, build=True):
...     print(line, end="")

# Stream run logs in real time until the server closes the stream (Ctrl-C to stop)
>>> for line in api.fetch_space_logs(repo_id=repo_id, follow=True):
...     print(line, end="")
```

The same functionality is available from the CLI:

```bash
hf spaces logs username/my-space             # drain run logs
hf spaces logs username/my-space --build     # read build logs
hf spaces logs username/my-space -f          # stream in real time
hf spaces logs username/my-space -n 50       # last 50 lines only
```

### SSH into a Space (Dev Mode)

[Dev Mode](https://huggingface.co/docs/hub/spaces-dev-mode) lets you SSH into a running Space container for live debugging and development. Use `hf spaces ssh` to open a session directly from the terminal. If Dev Mode is not yet enabled on the Space, the CLI will prompt you to enable it (or pass `--auto` to skip the prompt).

Your SSH public key must be registered at [in your settings](https://huggingface.co/settings/keys).

```bash
# SSH into a Space (enables Dev Mode if needed)
hf spaces ssh username/my-space

# Auto-enable Dev Mode without prompting
hf spaces ssh username/my-space --auto

# Print the SSH command without running it
hf spaces ssh username/my-space --dry-run

# Use a specific SSH key
hf spaces ssh username/my-space -i ~/.ssh/id_ed25519
```

You can also enable Dev Mode without SSH using `hf spaces dev-mode`, which prints connection instructions for SSH, VS Code, Cursor, and Windsurf:

```bash
hf spaces dev-mode username/my-space
```

### Mount volumes in your Space

You can mount Hub resources (models, datasets, or storage buckets) as volumes in your Space's container. This gives your Space direct filesystem access to these resources without having to download them in your code. Volumes can be set directly when creating or duplicating a Space:

```py
>>> from huggingface_hub import Volume
>>> api.create_repo(
...     repo_id=repo_id,
...     repo_type="space",
...     space_sdk="gradio",
...     space_volumes=[
...         Volume(type="model", source="username/my-model", mount_path="/models", read_only=True),
...         Volume(type="bucket", source="username/my-bucket", mount_path="/data"),
...     ],
... )
```
```py
>>> api.duplicate_repo(
...     from_id=repo_id,
...     repo_type="space",
...     space_volumes=[
...         Volume(type="model", source="username/my-model", mount_path="/models", read_only=True),
...         Volume(type="bucket", source="username/my-bucket", mount_path="/data"),
...     ],
... )
```

You can check which volumes are currently mounted via the Space runtime:

```py
>>> runtime = api.get_space_runtime(repo_id=repo_id)
>>> runtime.volumes
[Volume(type='model', source='username/my-model', mount_path='/models', read_only=True), ...]
```

If you need to update volumes on an existing Space, use [`set_space_volumes`]. Note that this replaces all previously mounted volumes.

```py
>>> api.set_space_volumes(
...     repo_id=repo_id,
...     volumes=[
...         Volume(type="model", source="username/my-model", mount_path="/models", read_only=True),
...         Volume(type="dataset", source="username/my-dataset", mount_path="/data", read_only=True),
...         Volume(type="bucket", source="username/my-bucket", mount_path="/output"),
...     ],
... )
```

To remove all volumes from your Space:

```py
>>> api.delete_space_volumes(repo_id=repo_id)
```

> [!NOTE]
> Models, datasets, and Spaces are always mounted as read-only. Only storage buckets support read-write mounts.

> [!WARNING]
> Setting volumes replaces any previously mounted volumes. To add a volume to an existing list, first read the current volumes from the runtime and include them in the new list.

All volume operations are also available from the CLI:

```bash
# List current volumes
hf spaces volumes ls username/my-space

# Set (replace) volumes
hf spaces volumes set username/my-space \
    -v hf://models/username/my-model:/models \
    -v hf://buckets/username/my-bucket:/data

# Remove all volumes
hf spaces volumes delete username/my-space
```

## More advanced: temporarily upgrade your Space !

Spaces allow for a lot of different use cases. Sometimes, you might want
to temporarily run a Space on a specific hardware, do something and then shut it down. In
this section, we will explore how to benefit from Spaces to finetune a model on demand.
This is only one way of solving this particular problem. It has to be taken as a suggestion
and adapted to your use case.

Let's assume we have a Space to finetune a model. It is a Gradio app that takes as input
a model id and a dataset id. The workflow is as follows:

0. (Prompt the user for a model and a dataset)
1. Load the model from the Hub.
2. Load the dataset from the Hub.
3. Finetune the model on the dataset.
4. Upload the new model to the Hub.

Step 3. requires a custom hardware but you don't want your Space to be running all the time on a paid
GPU. A solution is to dynamically request hardware for the training and shut it
down afterwards. Since requesting hardware restarts your Space, your app must somehow "remember"
the current task it is performing. There are multiple ways of doing this. In this guide
we will see one solution using a Dataset as "task scheduler".

### App skeleton

Here is what your app would look like. On startup, check if a task is scheduled and if yes,
run it on the correct hardware. Once done, set back hardware to the free-plan CPU and
prompt the user for a new task.

> [!WARNING]
> Such a workflow does not support concurrent access as normal demos.
> In particular, the interface will be disabled when training occurs.
> It is preferable to set your repo as private to ensure you are the only user.

```py
# Space will need your token to request hardware: set it as a Secret !
HF_TOKEN = os.environ.get("HF_TOKEN")

# Space own repo_id
TRAINING_SPACE_ID = "Wauplin/dreambooth-training"

from huggingface_hub import HfApi, SpaceHardware
api = HfApi(token=HF_TOKEN)

# On Space startup, check if a task is scheduled. If yes, finetune the model. If not,
# display an interface to request a new task.
task = get_task()
if task is None:
    # Start Gradio app
    def gradio_fn(task):
        # On user request, add task and request hardware
        add_task(task)
        api.request_space_hardware(repo_id=TRAINING_SPACE_ID, hardware=SpaceHardware.T4_MEDIUM)

    gr.Interface(fn=gradio_fn, ...).launch()
else:
    runtime = api.get_space_runtime(repo_id=TRAINING_SPACE_ID)
    # Check if Space is loaded with a GPU.
    if runtime.hardware == SpaceHardware.T4_MEDIUM:
        # If yes, finetune base model on dataset !
        train_and_upload(task)

        # Then, mark the task as "DONE"
        mark_as_done(task)

        # DO NOT FORGET: set back CPU hardware
        api.request_space_hardware(repo_id=TRAINING_SPACE_ID, hardware=SpaceHardware.CPU_BASIC)
    else:
        api.request_space_hardware(repo_id=TRAINING_SPACE_ID, hardware=SpaceHardware.T4_MEDIUM)
```

### Task scheduler

Scheduling tasks can be done in many ways. Here is an example how it could be done using
a simple CSV stored as a Dataset.

```py
# Dataset ID in which a `tasks.csv` file contains the tasks to perform.
# Here is a basic example for `tasks.csv` containing inputs (base model and dataset)
# and status (PENDING or DONE).
#     multimodalart/sd-fine-tunable,Wauplin/concept-1,DONE
#     multimodalart/sd-fine-tunable,Wauplin/concept-2,PENDING
TASK_DATASET_ID = "Wauplin/dreambooth-task-scheduler"

def _get_csv_file():
    return hf_hub_download(repo_id=TASK_DATASET_ID, filename="tasks.csv", repo_type="dataset", token=HF_TOKEN)

def get_task():
    with open(_get_csv_file()) as csv_file:
        csv_reader = csv.reader(csv_file, delimiter=',')
        for row in csv_reader:
            if row[2] == "PENDING":
                return row[0], row[1] # model_id, dataset_id

def add_task(task):
    model_id, dataset_id = task
    with open(_get_csv_file()) as csv_file:
        with open(csv_file, "r") as f:
            tasks = f.read()

    api.upload_file(
        repo_id=repo_id,
        repo_type=repo_type,
        path_in_repo="tasks.csv",
        # Quick and dirty way to add a task
        path_or_fileobj=(tasks + f"\n{model_id},{dataset_id},PENDING").encode()
    )

def mark_as_done(task):
    model_id, dataset_id = task
    with open(_get_csv_file()) as csv_file:
        with open(csv_file, "r") as f:
            tasks = f.read()

    api.upload_file(
        repo_id=repo_id,
        repo_type=repo_type,
        path_in_repo="tasks.csv",
        # Quick and dirty way to set the task as DONE
        path_or_fileobj=tasks.replace(
            f"{model_id},{dataset_id},PENDING",
            f"{model_id},{dataset_id},DONE"
        ).encode()
    )
```
