---
description: >-
  Manage Verda's S3-compatible object storage from the command line. The verda
  object-storage commands provide AWS-CLI-style operations for buckets and
  objects, with interactive wizards and resumable large transfers.
revision_date: 22.06.2026
---

# Object Storage

The `verda object-storage` commands provide AWS-CLI-style access to Verda's S3-compatible object storage (the `oss` alias is shorter: `verda oss ls`). They use a separate credential set (keys prefixed `verda_s3_`) so object-storage access is independent of your main API credentials while still sharing the profile system.

Every command works two ways: **non-interactively** with positional URIs and flags (for scripts, pipes, and AI agents), and **interactively** with a TUI when you omit the target on a terminal.

***

#### Command reference

| Command | Description |
|---------|-------------|
| `verda object-storage configure` | Set up object-storage credentials (wizard or flags) |
| `verda object-storage show` | Print active object-storage credential status (no secrets) |
| `verda object-storage ls` | List buckets, or list keys under a prefix |
| `verda object-storage cp` | Copy between local and object storage, or between object-storage locations |
| `verda object-storage mv` | Move (copy + delete source) |
| `verda object-storage rm` | Delete one or many keys |
| `verda object-storage sync` | Sync a directory and a prefix in either direction |
| `verda object-storage mb` | Make bucket |
| `verda object-storage rb` | Remove bucket (optionally force-empty first) |
| `verda object-storage presign` | Generate a time-limited GET URL for a key |
| `verda object-storage ls-uploads` | List incomplete multipart uploads (resumable) |
| `verda object-storage abort-uploads` | Abort incomplete multipart uploads |

***

#### Configure credentials

First create an access key in the Verda dashboard: **log in → select your project → Project management → Credentials → Object Storage Access Keys.**

Run the interactive wizard. The endpoint and region come pre-filled with defaults (`https://objects.fin-03.verda.storage`, `us-east-1`), so you normally just pick a profile and paste the access key and secret:

```bash
verda object-storage configure
```

Non-interactive (endpoint defaults if omitted; pass `--endpoint` for another region):

```bash
verda object-storage configure \
  --access-key AKIA... \
  --secret-key ...
```

Show the active configuration (no secrets are printed):

```bash
verda object-storage show
verda object-storage show --profile staging
```

| Flag | Description |
|------|-------------|
| `--access-key` | Object-storage access key ID |
| `--secret-key` | Object-storage secret access key |
| `--endpoint` | Object-storage endpoint URL (defaults to `https://objects.fin-03.verda.storage`) |
| `--region` | Region (defaults to `us-east-1`) |
| `--profile` | Profile to write to (defaults to the active profile) |
| `--credentials-file` | Override the credentials file path |

Object-storage credentials are stored in the same credentials file as your API credentials (`~/.verda/credentials`), using `verda_s3_`-prefixed keys.

***

#### List buckets and objects

```bash
verda object-storage ls                              # list all buckets
verda object-storage ls s3://my-bucket               # top-level keys + prefixes (delimiter /)
verda object-storage ls s3://my-bucket --recursive   # every key under the bucket
verda object-storage ls s3://my-bucket --human-readable --summarize
```

On a terminal, running `verda object-storage ls` with no argument opens an interactive folder browser — drill into prefixes, run per-object actions, or multi-select objects to download.

![Browsing buckets and objects with the interactive object-storage browser](https://docs.verda.com/assets/cli-object-storage-browse.gif)

***

#### Copy files

```bash
# upload
verda object-storage cp ./local-file s3://my-bucket/key.txt

# download
verda object-storage cp s3://my-bucket/key.txt ./local-file

# server-side copy (remote to remote, no data traverses the client)
verda object-storage cp s3://src-bucket/key s3://dst-bucket/key

# recursive directory upload
verda object-storage cp ./dir s3://my-bucket/prefix/ --recursive

# with include/exclude filters (match against relative path; * does not cross /)
verda object-storage cp ./dir s3://my-bucket/prefix/ --recursive \
  --include '*.go' --exclude '*_test.go'

# override content-type (otherwise inferred from extension)
verda object-storage cp ./file s3://my-bucket/key --content-type 'application/json'

# preview what would happen
verda object-storage cp ./dir s3://my-bucket/prefix/ --recursive --dryrun

# tune throughput for large transfers
verda object-storage cp ./big.bin s3://my-bucket/big.bin --concurrency 16 --part-size 32MiB
```

| Flag | Description |
|------|-------------|
| `--recursive` | Copy a directory or prefix recursively |
| `--include` / `--exclude` | Filter by relative path (`filepath.Match`; `*` does not cross `/`) |
| `--content-type` | Override the content-type (otherwise inferred from extension) |
| `--dryrun` | Print what would happen without transferring |
| `--concurrency` | Number of concurrent parts for large transfers |
| `--part-size` | Multipart part size (e.g. `32MiB`) |
| `--no-resume` | Force a fresh transfer, ignoring any saved progress |

To upload without typing an `s3://` URI, run `verda object-storage cp <file>` with no destination on a terminal — a wizard picks the destination bucket and folder for you:

![Uploading with the cp wizard — pick a bucket and folder instead of typing the s3:// URI](https://docs.verda.com/assets/cli-object-storage-upload-wizard.gif)

***

#### Resumable large transfers

Single-file uploads and single-object downloads larger than the part size are multipart, parallel (5 concurrent parts by default), and **resumable**. If a transfer is interrupted (network drop, `Ctrl+C`, crash), **re-run the exact same command** and it continues — only the missing parts are sent or fetched:

```bash
# upload; if it breaks, run the SAME command again to resume
verda object-storage cp ./model.safetensors s3://my-bucket/models/model.safetensors

# download; re-run to resume (a partial <dest>.part is kept until it completes)
verda object-storage cp s3://my-bucket/models/model.safetensors ./model.safetensors

# force a fresh transfer, ignoring any saved progress
verda object-storage cp s3://my-bucket/models/model.safetensors ./model.safetensors --no-resume
```

![Re-running an interrupted upload resumes only the missing parts](https://docs.verda.com/assets/cli-object-storage-upload-resume.gif)

A few details worth knowing:

* Resume reuses the **same part size** that the interrupted run used. Passing a different `--part-size` (or changing the file) is detected and the transfer restarts cleanly. Part sizes accept binary forms (`MiB`, `GiB`); the loose `MB`/`M` forms are also treated as binary (`1MB` = 1048576 bytes).
* Resume state lives locally: uploads under `~/.verda/s3-uploads/`, downloads under `~/.verda/s3-downloads/` (plus a `<dest>.part` file). The key is a hash of the **source path + destination**, so resume requires re-running with the same source and destination.
* Incomplete **uploads** stage parts on the server that cost storage until completed or aborted. Use `verda object-storage ls-uploads` to list them (and pick one to resume) and `verda object-storage abort-uploads` to clean them up.

    ![Uploading with the cp wizard, interrupting, then resuming from the object-storage ls-uploads picker](https://docs.verda.com/assets/cli-object-storage-resume-uploads.gif)

* **No silent overwrites:** if a local file of the same name already exists, an interactive download is saved as `name-2.ext`, `name-3.ext`, … A genuine resume of the *same* object keeps its original name so its `.part` is continued.
* Recursive (`--recursive`), `sync`, and `mv` transfers are not yet resumable per-file.

***

#### Move files

`mv` has the same flag surface as `cp`; the source is removed on success.

```bash
verda object-storage mv ./tmpfile s3://my-bucket/final-name
verda object-storage mv s3://my-bucket/old-key s3://my-bucket/new-key
```

On a terminal, `verda object-storage mv` with no arguments (or a single `s3://` source) opens a remote move/rename wizard. Local ↔ remote moves always require both explicit arguments.

![Moving an object with the interactive move wizard](https://docs.verda.com/assets/cli-object-storage-mv.gif)

***

#### Sync directories

```bash
# local to object storage
verda object-storage sync ./local-dir s3://my-bucket/prefix/

# object storage to local
verda object-storage sync s3://my-bucket/prefix/ ./local-dir

# remove destination files that don't exist in source (AWS-convention --delete)
verda object-storage sync ./a s3://bucket/ --delete

# treat any mtime difference as "changed" (not just newer-source)
verda object-storage sync ./a s3://bucket/ --exact-timestamps --dryrun
```

By default, sync copies a file when the source size differs or the source is newer. `--exact-timestamps` instead copies on any mtime difference.

***

#### Remove objects

```bash
verda object-storage rm s3://bucket/key
verda object-storage rm s3://bucket/prefix/ --recursive
verda object-storage rm s3://bucket/prefix/ --recursive --include '*.log' --yes
verda object-storage rm s3://bucket/prefix/ --recursive --dryrun
```

!!! warning
    `rm` is destructive and prompts for confirmation unless `--yes` is passed. In agent mode, `--yes` is mandatory.

![Deleting objects with the interactive browser — multi-select and a confirmation guard](https://docs.verda.com/assets/cli-object-storage-rm.gif)

***

#### Buckets

```bash
verda object-storage mb s3://new-bucket-name

verda object-storage rb s3://old-bucket              # only works if empty
verda object-storage rb s3://old-bucket --force      # empty the bucket first, then remove
```

`rb` prompts for confirmation unless `--yes`. `--force` implies a recursive delete of the bucket contents, so be sure before using it.

***

#### Presigned URLs

Generate a time-limited GET URL for an object:

```bash
verda object-storage presign s3://bucket/key                    # default 1h
verda object-storage presign s3://bucket/key --expires-in 15m
verda object-storage presign s3://bucket/key --expires-in 24h
```

The URL is printed to stdout (pipe-friendly) and the expiration hint goes to stderr, so it's safe to pipe:

```bash
verda object-storage presign s3://bucket/key --expires-in 30m | pbcopy
```

***

#### Multiple profiles

Profiles work across both API and object-storage credentials. Create a second profile via:

```bash
verda object-storage configure --profile staging
```

`configure` and `show` take a local `--profile` flag. The other commands (`ls`, `cp`, `rm`, …) have no local `--profile` flag — select a profile with the global `--auth.profile staging`, the `VERDA_PROFILE=staging` environment variable, or persist it with `verda auth use staging`.

!!! info
    `configure` writes to the profile you pick or name; it does **not** auto-follow the active profile the way the read commands do. If you create credentials for a non-active profile, point the read commands at it with `--auth.profile <name>`, `VERDA_PROFILE`, or switch with `verda auth use <name>`.

***

#### Output formats

All commands honour the global output flags:

* `--output table` (default)
* `--output json` / `--output yaml` — a single structured payload at the end
* `--agent` — disables interactive prompts, implies JSON, and requires `--yes` for destructive operations
* `--debug` — dumps SDK request/response metadata to stderr

Per-file progress lines (`uploaded`, `downloaded`, `copied`, `moved`, `deleted`) are only emitted when the format is `table`. Structured output produces exactly one payload so it stays parseable.

***

#### Interactive vs non-interactive

The interactive TUI triggers only when stdout is a terminal, you're not in `--agent` mode, and the output format is the default `table`. Otherwise an omitted target returns the command help (or a structured error in `--agent`).

| Command | Interactive trigger (on a TTY) | Flow |
|---------|--------------------------------|------|
| `configure` | any of `--access-key`/`--secret-key`/`--endpoint` missing | credential wizard |
| `ls` | no argument | folder browser (drill in, per-object actions, multi-download) |
| `cp` | no destination (and not a bare `s3://` download) | upload wizard (source → bucket → folder → confirm) |
| `mb` | no argument | prompts for the new bucket name |
| `rb` | no argument | bucket picker, then the destructive confirm |
| `rm` | no argument | folder browser; tick files at a level to delete (confirm + preview) |
| `mv` | no args, or a single `s3://` source | remote move/rename wizard |

`Esc` steps back (ascends a folder or returns to the previous wizard step); `Ctrl+C` exits immediately.

***

#### Environment variables

| Variable | Description |
|----------|-------------|
| `VERDA_SHARED_CREDENTIALS_FILE` | Override the default credentials path (`~/.verda/credentials`) |
| `VERDA_PROFILE` | Select the active profile for read/transfer commands |
