> ## Documentation Index
> Fetch the complete documentation index at: https://docs.meshagent.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Storage

## Overview

The `StorageClient` is the Room API for room-scoped files and folders. Use it to store shared inputs, outputs, artifacts, and attachments that humans, agents, and services in the same room need to read or write.

## CLI commands

Start with the CLI help, then use a few common commands:

```bash bash theme={null}
meshagent room storage --help
meshagent room storage ls --room myroom room://
meshagent room storage cp ./report.pdf room://artifacts/report.pdf --room myroom
meshagent room storage get --room myroom room://artifacts/report.pdf
```

## Why use the Storage API?

* Share files between people, agents, and deployed services without wiring separate object storage access.
* Store outputs such as reports, logs, generated media, or intermediate artifacts.
* Download file contents directly or get a downloadable URL when you need to hand a file to another system.

## How it works

Paths are relative to the room storage root. Use `upload` or `uploadStream` to write files, `download` or `downloadStream` to read them back, and `download_url` / `downloadUrl` when you need a fetchable URL for another system. The API also emits `file.updated`, `file.deleted`, and `file.moved` events so other participants can react to changes.

## Permissions and grants

`upload` / `uploadStream` and `download` / `downloadStream` / `download_url` require a storage grant on the participant token for the target path. Storage grants are path-based, which lets you give a service access to only part of room storage. In the current server implementation, `list`, `exists`, `stat`, and `delete` are not gated by storage grants.

Use [API Scopes](../rest_api/api_scopes) and [Service YAML](../services/deployment/deploy_services) for the full scope model.

## Events

The storage system emits three types of events:

* **`file.updated`**\
  Triggered when a file is created or updated. Python and .NET handlers receive `path` and `participant_id`. JS/TS and Dart handlers receive a `FileUpdatedEvent` with `.path`.

<CodeGroup>
  ```python Python theme={null}
  def on_file_updated(path: str, participant_id: str):
     print(f"File updated: {path} by {participant_id}")

  room.storage.on("file.updated", on_file_updated)

  ```

  ```javascript NodeJs theme={null}
  function onFileUpdated(event) {
      console.log("File updated:", event.path);
  }

  room.storage.on("file.updated", onFileUpdated);

  ```

  ```typescript TypeScript theme={null}
  function onFileUpdated(event: FileUpdatedEvent) {
      console.log("File updated:", event.path);
  }

  room.storage.on("file.updated", onFileUpdated);

  ```

  ```dart Dart theme={null}
  void onFileUpdated(String path) {
      print("File updated: $path");
  }

  room.listen((RoomEvent event) {
      if (event is FileUpdatedEvent) {
          onFileUpdated(event.path);
      }
  });

  ```

  ```dotnet C# theme={null}
  void OnFileUpdated(Dictionary<string, object?> data)
  {
      Console.WriteLine($"File updated: {data["path"]} by {data["participant_id"]}");
  }

  room.Storage.On("file.updated", OnFileUpdated);

  ```
</CodeGroup>

* **`file.deleted`**\
  Triggered when a file is deleted. Python and .NET handlers receive `path` and `participant_id`. JS/TS and Dart handlers receive a `FileDeletedEvent` with `.path`.

<CodeGroup>
  ```python Python theme={null}
  def on_file_deleted(path: str, participant_id: str):
     print(f"File deleted: {path} by {participant_id}")

  room.storage.on("file.deleted", on_file_deleted)

  ```

  ```javascript NodeJs theme={null}
  function onFileDeleted(event) {
      console.log("File deleted:", event.path);
  }

  room.storage.on("file.deleted", onFileDeleted);

  ```

  ```typescript TypeScript theme={null}
  function onFileDeleted(event: FileDeletedEvent) {
      console.log("File deleted:", event.path);
  }

  room.storage.on("file.deleted", onFileDeleted);

  ```

  ```dart Dart theme={null}
  void onFileDeleted(String path) {
      print("File deleted: $path");
  }

  room.listen((RoomEvent event) {
      if (event is FileDeletedEvent) {
          onFileDeleted(event.path);
      }
  });

  ```

  ```dotnet C# theme={null}
  void OnFileDeleted(Dictionary<string, object?> data)
  {
      Console.WriteLine($"File deleted: {data["path"]} by {data["participant_id"]}");
  }

  room.Storage.On("file.deleted", OnFileDeleted);

  ```
</CodeGroup>

* **`file.moved`**
  Triggered when a file or folder is moved. Python and .NET handlers receive `source_path`, `destination_path`, and `participant_id`. JS/TS and Dart handlers receive a `FileMovedEvent` with `.sourcePath` and `.destinationPath`.

<CodeGroup>
  ```python Python theme={null}
  def on_file_moved(source_path: str, destination_path: str, participant_id: str):
     print(f"File moved: {source_path} -> {destination_path} by {participant_id}")

  room.storage.on("file.moved", on_file_moved)

  ```

  ```javascript NodeJs theme={null}
  function onFileMoved(event) {
      console.log("File moved:", event.sourcePath, "->", event.destinationPath);
  }

  room.storage.on("file.moved", onFileMoved);

  ```

  ```typescript TypeScript theme={null}
  function onFileMoved(event: FileMovedEvent) {
      console.log("File moved:", event.sourcePath, "->", event.destinationPath);
  }

  room.storage.on("file.moved", onFileMoved);

  ```

  ```dart Dart theme={null}
  void onFileMoved(String sourcePath, String destinationPath) {
      print("File moved: $sourcePath -> $destinationPath");
  }

  room.listen((RoomEvent event) {
      if (event is FileMovedEvent) {
          onFileMoved(event.sourcePath, event.destinationPath);
      }
  });

  ```

  ```dotnet C# theme={null}
  void OnFileMoved(Dictionary<string, object?> data)
  {
      Console.WriteLine($"File moved: {data["source_path"]} -> {data["destination_path"]} by {data["participant_id"]}");
  }

  room.Storage.On("file.moved", OnFileMoved);

  ```
</CodeGroup>

You can remove an event handler with:

<CodeGroup>
  ```python Python theme={null}
  room.storage.off("file.updated", on_file_updated)
  room.storage.off("file.deleted", on_file_deleted)
  room.storage.off("file.moved", on_file_moved)

  ```

  ```javascript NodeJs theme={null}
  room.storage.off("file.updated", onFileUpdated);
  room.storage.off("file.deleted", onFileDeleted);
  room.storage.off("file.moved", onFileMoved);

  ```

  ```typescript TypeScript theme={null}
  room.storage.off("file.updated", onFileUpdated);
  room.storage.off("file.deleted", onFileDeleted);
  room.storage.off("file.moved", onFileMoved);

  ```

  ```dart Dart theme={null}
  room.listen((RoomEvent event) {
      // Remove event handlers as needed
      if (event is FileUpdatedEvent) {
          // Remove handler logic
      }
      if (event is FileDeletedEvent) {
          // Remove handler logic
      }
  });

  ```

  ```dotnet C# theme={null}
  room.Storage.Off("file.updated", OnFileUpdated);
  room.Storage.Off("file.deleted", OnFileDeleted);
  room.Storage.Off("file.moved", OnFileMoved);

  ```
</CodeGroup>

## API reference

Use the methods below to inspect room storage, upload and download files, and clean up stored artifacts. Each method is asynchronous, so you should `await` the call.

### `exists(path)`

**Description**
Checks if a file or folder exists at the given path.

**Parameters**

* `path` *(str)*: The path to check.

**Returns**

* *(bool)*: `True` if the file or folder exists; `False` otherwise.

**Example**:

<CodeGroup>
  ```bash CLI theme={null}
  meshagent room storage exists \
    --room myroom \
    room://folder/data.json

  ```

  ```python Python theme={null}
  if await room.storage.exists(path="folder/data.json"):
      print("Data file exists!")
  else:
      print("Data file does not exist.")

  ```

  ```javascript NodeJs theme={null}
  if (await room.storage.exists("folder/data.json")) {
      console.log("Data file exists!");
  } else {
      console.log("Data file does not exist.");
  }

  ```

  ```typescript TypeScript theme={null}
  if (await room.storage.exists("folder/data.json")) {
      console.log("Data file exists!");
  } else {
      console.log("Data file does not exist.");
  }

  ```

  ```dart Dart theme={null}
  if (await room.storage.exists("folder/data.json")) {
      print("Data file exists!");
  } else {
      print("Data file does not exist.");
  }

  ```

  ```dotnet C# theme={null}
  if (await room.Storage.Exists("folder/data.json"))
  {
      Console.WriteLine("Data file exists!");
  }
  else
  {
      Console.WriteLine("Data file does not exist.");
  }

  ```
</CodeGroup>

### `stat(path)`

**Description**\
Fetch basic metadata (exists, folder flag, created/updated timestamps) for a file or folder.

**Parameters**

* `path` *(str)*: The path to inspect.

**Returns**

* `StorageEntry | None`: Entry when present; `None` if not found.

The server returns `name`, `is_folder`, `created_at`, and `updated_at` fields. SDKs that expose `stat` parse timestamps into native datetime types.

**Example**:

<CodeGroup>
  ```python Python theme={null}
  entry = await room.storage.stat(path="folder/data.json")
  if entry:
      print(entry.name, entry.is_folder, entry.created_at, entry.updated_at)

  ```

  ```javascript NodeJs theme={null}
  const entry = await room.storage.stat("folder/data.json");
  if (entry) {
      console.log(entry.name, entry.isFolder, entry.createdAt, entry.updatedAt);
  }

  ```

  ```typescript TypeScript theme={null}
  const entry = await room.storage.stat("folder/data.json");
  if (entry) {
      console.log(entry.name, entry.isFolder, entry.createdAt, entry.updatedAt);
  }

  ```

  ```dart Dart theme={null}
  final entry = await room.storage.stat("folder/data.json");
  if (entry != null) {
      print("${entry.name} ${entry.isFolder} ${entry.createdAt} ${entry.updatedAt}");
  }

  ```
</CodeGroup>

* **Availability**: Python, JavaScript/TypeScript, and Dart SDKs expose `stat`.

### `upload(path, data, overwrite=False)`

**Description**\
Uploads a complete file payload in one call. Use this when you already have the bytes in memory.

**Parameters**

* `path` *(str)*: The destination file path.
* `data` *(bytes / Uint8Array / byte\[])*: The file contents to write.
* `overwrite` *(bool, optional)*: Whether to replace an existing file at that path.

**Returns**

* `None`

**Example**:

<CodeGroup>
  ```python Python theme={null}
  data_to_write = b"Hello, Storage!"

  await room.storage.upload(
      path="files/new.txt",
      data=data_to_write,
      overwrite=True,
  )

  ```

  ```javascript NodeJs theme={null}
  const dataToWrite = new TextEncoder().encode("Hello, Storage!");

  await room.storage.upload("files/new.txt", dataToWrite, {
    overwrite: true,
  });

  ```

  ```typescript TypeScript theme={null}
  const dataToWrite = new TextEncoder().encode("Hello, Storage!");

  await room.storage.upload("files/new.txt", dataToWrite, {
    overwrite: true,
  });

  ```

  ```dart Dart theme={null}
  final dataToWrite = Uint8List.fromList(utf8.encode("Hello, Storage!"));

  await room.storage.upload(
    "files/new.txt",
    dataToWrite,
    overwrite: true,
  );

  ```

  ```dotnet C# theme={null}
  var dataToWrite = Encoding.UTF8.GetBytes("Hello, Storage!");

  await room.Storage.Upload(
      "files/new.txt",
      dataToWrite,
      overwrite: true
  );

  ```
</CodeGroup>

### `upload_stream(path, chunks, ...)`

**Description**\
Streams file contents chunk-by-chunk. Use this when you do not want to materialize the whole file in memory at once.

**Parameters**

* `path` *(str)*: The destination file path.
* `chunks`: An async iterable / stream / `IAsyncEnumerable<byte[]>` of file chunks.
* `overwrite` *(optional)*: Replace an existing file at that path.
* `size` *(optional)*: Total byte size when you already know it.
* `chunk_size` / `chunkSize` *(optional)*: Pull size for streamed uploads.

**Returns**

* `None`

**Example**:

<CodeGroup>
  ```python Python theme={null}
  async def chunks():
      yield b"Hello, "
      yield b"world!"


  await room.storage.upload_stream(
      path="logs/output.txt",
      chunks=chunks(),
      overwrite=True,
      size=13,
  )

  ```

  ```javascript NodeJs theme={null}
  async function* chunks() {
    yield new TextEncoder().encode("Hello, ");
    yield new TextEncoder().encode("world!");
  }

  await room.storage.uploadStream("logs/output.txt", chunks(), {
    overwrite: true,
    size: 13,
  });

  ```

  ```typescript TypeScript theme={null}
  async function* chunks(): AsyncGenerator<Uint8Array> {
    yield new TextEncoder().encode("Hello, ");
    yield new TextEncoder().encode("world!");
  }

  await room.storage.uploadStream("logs/output.txt", chunks(), {
    overwrite: true,
    size: 13,
  });

  ```

  ```dart Dart theme={null}
  Stream<Uint8List> chunks() async* {
    yield Uint8List.fromList(utf8.encode("Hello, "));
    yield Uint8List.fromList(utf8.encode("world!"));
  }

  await room.storage.uploadStream(
    "logs/output.txt",
    chunks(),
    overwrite: true,
    size: 13,
  );

  ```

  ```dotnet C# theme={null}
  async IAsyncEnumerable<byte[]> Chunks()
  {
      yield return Encoding.UTF8.GetBytes("Hello, ");
      yield return Encoding.UTF8.GetBytes("world!");
      await Task.CompletedTask;
  }

  await room.Storage.UploadStream(
      "logs/output.txt",
      Chunks(),
      overwrite: true,
      size: 13
  );

  ```
</CodeGroup>

### `download_stream(path, chunk_size=...)`

**Description**\
Streams a file back as metadata plus data chunks. Use this for larger files or when you want to process the response incrementally.

**Parameters**

* `path` *(str)*: The file path to download.
* `chunk_size` / `chunkSize` *(optional)*: Requested chunk size for streamed downloads.

**Returns**

* A stream / async iterator of binary chunks. The first chunk carries metadata such as file name, MIME type, and size.

**Example**:

<CodeGroup>
  ```python Python theme={null}
  stream = await room.storage.download_stream(path="files/report.pdf")

  async for chunk in stream:
      if chunk.headers["kind"] == "start":
          print(chunk.headers["name"], chunk.headers["size"])
          continue
      print(f"Received {len(chunk.data)} bytes")

  ```

  ```javascript NodeJs theme={null}
  const stream = await room.storage.downloadStream("files/report.pdf");

  for await (const chunk of stream) {
    if (chunk.headers.kind === "start") {
      console.log(chunk.headers.name, chunk.headers.size);
      continue;
    }
    console.log(`Received ${chunk.data.length} bytes`);
  }

  ```

  ```typescript TypeScript theme={null}
  const stream = await room.storage.downloadStream("files/report.pdf");

  for await (const chunk of stream) {
    if (chunk.headers.kind === "start") {
      console.log(chunk.headers.name, chunk.headers.size);
      continue;
    }
    console.log(`Received ${chunk.data.length} bytes`);
  }

  ```

  ```dart Dart theme={null}
  final stream = await room.storage.downloadStream("files/report.pdf");

  await for (final chunk in stream) {
    if (chunk.headers["kind"] == "start") {
      print("${chunk.headers["name"]} ${chunk.headers["size"]}");
      continue;
    }
    print("Received ${chunk.data.length} bytes");
  }

  ```

  ```dotnet C# theme={null}
  await foreach (var chunk in room.Storage.DownloadStream("files/report.pdf"))
  {
      if ((chunk.Headers["kind"] as string) == "start")
      {
          Console.WriteLine($"{chunk.Headers["name"]} {chunk.Headers["size"]}");
          continue;
      }

      Console.WriteLine($"Received {chunk.Data.Length} bytes");
  }

  ```
</CodeGroup>

### `download(path)`

**Description**\
Retrieves the content of a file from the remote storage. This loads the entire file into memory; use `download_url` for large files or streaming.

**Parameters**

* `path` *(str)*: The file path to download.

**Returns**

* *File-like Response*: Contains the file's raw data, typically accessible through a `.data` property.

**Example**:

<CodeGroup>
  ```bash CLI theme={null}
  meshagent room storage cp \
    room://files/data.bin \
    ./data.bin \
    --room myroom

  ```

  ```python Python theme={null}
  file_response = await room.storage.download(path="files/data.bin")
  print(file_response.data)  # raw bytes

  ```

  ```javascript NodeJs theme={null}
  const fileResponse = await room.storage.download("files/data.bin");
  console.log(fileResponse.data);  // raw bytes

  ```

  ```typescript TypeScript theme={null}
  const fileResponse = await room.storage.download("files/data.bin");
  console.log(fileResponse.data);  // raw bytes

  ```

  ```dart Dart theme={null}
  final fileResponse = await room.storage.download("files/data.bin");
  print(fileResponse.data);  // raw bytes

  ```

  ```dotnet C# theme={null}
  var fileResponse = await room.Storage.Download("files/data.bin");
  Console.WriteLine(fileResponse.Data);  // raw bytes

  ```
</CodeGroup>

### `download_url(path)`

**Description**\
Requests a downloadable URL for the specified file path, which can be used to fetch the file directly (e.g., via HTTP). The exact protocol or format of the returned URL may vary, and may be a signed URL if the backing storage provider supports it.

**Parameters**

* `path` *(str)*: The file path to retrieve a download URL for.

**Returns**

* *(str)*: A URL string you can fetch with your own HTTP or other suitable client.

**Example**:

<CodeGroup>
  ```python Python theme={null}
  url = await room.storage.download_url(path="files/report.pdf")
  print("Download the file from:", url)

  ```

  ```javascript NodeJs theme={null}
  const url = await room.storage.downloadUrl("files/report.pdf");
  console.log("Download the file from:", url);

  ```

  ```typescript TypeScript theme={null}
  const url = await room.storage.downloadUrl("files/report.pdf");
  console.log("Download the file from:", url);

  ```

  ```dart Dart theme={null}
  final url = await room.storage.downloadUrl("files/report.pdf");
  print("Download the file from: $url");

  ```

  ```dotnet C# theme={null}
  var url = await room.Storage.DownloadUrl("files/report.pdf");
  Console.WriteLine($"Download the file from: {url}");

  ```
</CodeGroup>

### `list(path)`

**Description**\
Lists the contents of a folder, returning file and subfolder names along with a flag indicating if each entry is a folder. The server also returns `created_at` and `updated_at` timestamps; the Python SDK exposes them on `StorageEntry`.

**Parameters**

* `path` *(str)*: The folder path to list.

**Returns**

* *(list)*: A list of entries, each containing a `name` and `is_folder` property. Timestamps are available in SDKs that surface them.

**Example**:

<CodeGroup>
  ```bash CLI theme={null}
  meshagent room storage ls \
    --room myroom \
    room://some_folder

  ```

  ```python Python theme={null}
  entries = await room.storage.list(path="some_folder")
  for e in entries:
      print(e.name, "is folder?" if e.is_folder else "is file?")

  ```

  ```javascript NodeJs theme={null}
  const entries = await room.storage.list("some_folder");
  entries.forEach(e => {
      console.log(e.name, e.isFolder ? "is folder" : "is file");
  });

  ```

  ```typescript TypeScript theme={null}
  const entries = await room.storage.list("some_folder");
  entries.forEach(e => {
      console.log(e.name, e.isFolder ? "is folder" : "is file");
  });

  ```

  ```dart Dart theme={null}
  final entries = await room.storage.list("some_folder");
  for (var e in entries) {
      print("${e.name} ${e.isFolder ? "is folder" : "is file"}");
  }

  ```

  ```dotnet C# theme={null}
  var entries = await room.Storage.List("some_folder");
  foreach (var e in entries)
  {
      Console.WriteLine($"{e.Name} {(e.IsFolder ? \"is folder\" : \"is file\")}");
  }

  ```
</CodeGroup>

### `delete(path)`

**Description**\
Deletes a file or folder at the given path. A `file.deleted` event is typically emitted afterward. To delete a folder, pass `recursive=True` (Python) or call the raw `storage.delete` request with the `recursive` flag.

**Parameters**

* `path` *(str)*: The file path to delete.
* `recursive` *(bool, optional)*: Set `True` to delete folders recursively (Python helper or raw request).

**Returns**

* `None`

**Example**:

<CodeGroup>
  ```bash CLI theme={null}
  meshagent room storage rm \
    --room myroom \
    room://folder/old_file.txt

  ```

  ```python Python theme={null}
  await room.storage.delete("folder/old_file.txt")
  print("File deleted.")

  ```

  ```javascript NodeJs theme={null}
  await room.storage.delete("folder/old_file.txt");
  console.log("File deleted.");

  ```

  ```typescript TypeScript theme={null}
  await room.storage.delete("folder/old_file.txt");
  console.log("File deleted.");

  ```

  ```dart Dart theme={null}
  await room.storage.delete("folder/old_file.txt");
  print("File deleted.");

  ```

  ```dotnet C# theme={null}
  await room.Storage.Delete("folder/old_file.txt");
  Console.WriteLine("File deleted.");

  ```
</CodeGroup>

### `move(source_path, destination_path, overwrite=False)`

**Description**
Moves or renames a file or folder. A `file.moved` event is typically emitted afterward.

**Parameters**

* `source_path` *(str)*: The current file or folder path.
* `destination_path` *(str)*: The new file or folder path.
* `overwrite` *(bool, optional)*: Whether to replace an existing destination.

**Returns**

* `None`

**Example**:

<CodeGroup>
  ```python Python theme={null}
  await room.storage.move(
      source_path="folder/draft.json",
      destination_path="folder/final.json",
      overwrite=True,
  )

  ```

  ```javascript NodeJs theme={null}
  await room.storage.move("folder/draft.json", "folder/final.json", {
      overwrite: true,
  });

  ```

  ```typescript TypeScript theme={null}
  await room.storage.move("folder/draft.json", "folder/final.json", {
      overwrite: true,
  });

  ```

  ```dart Dart theme={null}
  await room.storage.move(
      "folder/draft.json",
      "folder/final.json",
      overwrite: true,
  );

  ```
</CodeGroup>

## Example Workflow

A common use case:

1. Check if a file exists.
2. Upload data if it doesn’t exist.
3. Later, download the file to verify or use the data.
4. Delete the file when it’s no longer needed, reacting to the `file.deleted` event.

<CodeGroup>
  ```python Python theme={null}
  import asyncio
  from meshagent.api import RoomClient


  async def main():
      # Run with:
      # meshagent room connect --room=my-room --identity=participant-name -- python3 storage-download.py
      async with RoomClient() as room:
          data_to_write = b"Hello, Storage!"

          if not await room.storage.exists(path="example.txt"):
              await room.storage.upload(
                  path="example.txt",
                  data=data_to_write,
                  overwrite=True,
              )

          response = await room.storage.download(path="example.txt")
          print("Downloaded content:", response.data)

          await room.storage.delete(path="example.txt")


  asyncio.run(main())

  ```

  ```javascript NodeJs theme={null}
  const data = new TextEncoder().encode("Hello, Storage!");

  if (!(await room.storage.exists("example.txt"))) {
      await room.storage.upload("example.txt", data, { overwrite: true });
  }

  const response = await room.storage.download("example.txt");
  console.log("Downloaded content:", new TextDecoder().decode(response.data));

  await room.storage.delete("example.txt");

  ```

  ```typescript TypeScript theme={null}
  import { RoomClient } from "@meshagent/meshagent";

  // Run with:
  // meshagent room connect --room=my-room --identity=participant-name -- <your node command>

  async function main() {
      const room = new RoomClient();

      try {
          await room.start();

          const data = new TextEncoder().encode("Hello, Storage!");
          if (!(await room.storage.exists("example.txt"))) {
              await room.storage.upload("example.txt", data, { overwrite: true });
          }

          const response = await room.storage.download("example.txt");
          console.log("Downloaded content:", new TextDecoder().decode(response.data));

          await room.storage.delete("example.txt");
      } catch (error) {
          console.error("Error starting the room client:", error);
      } finally {
          room.dispose();
      }
  }

  void main();

  ```

  ```dart Dart theme={null}
  import 'dart:convert';
  import 'dart:typed_data';

  import 'package:meshagent/meshagent.dart';

  Future<void> downloadStorageExample(RoomClient room) async {
    final data = Uint8List.fromList(utf8.encode('Hello, Storage!'));
    if (!await room.storage.exists('example.txt')) {
      await room.storage.upload('example.txt', data, overwrite: true);
    }

    final response = await room.storage.download('example.txt');
    print('Downloaded content: ${utf8.decode(response.data)}');

    await room.storage.delete('example.txt');
  }

  ```

  ```dotnet C# theme={null}
  var data = Encoding.UTF8.GetBytes("Hello, Storage!");
  if (!await room.Storage.Exists("example.txt"))
  {
      await room.Storage.Upload("example.txt", data, overwrite: true);
  }

  var response = await room.Storage.Download("example.txt");
  Console.WriteLine($"Downloaded content: {Encoding.UTF8.GetString(response.Data)}");

  await room.Storage.Delete("example.txt");

  ```
</CodeGroup>

This sequence demonstrates basic upload, read, and deletion flows within a single session.

## Related guides

* [Room API Overview](./overview)
* [Service YAML](../services/deployment/deploy_services)
