Skip to content

Client CLI: Upload, Download & Manage Objects

The stowry binary is both the server and the client. The client commands talk to a running server over HTTP, signing every request with the same key pair the server verifies.

Command Purpose
upload Send a file, or a whole directory
download Fetch a file, or stream it to stdout
list Page through the objects on the server
delete Remove one or more objects

upload, delete and list are routed only in store mode. In static and spa modes upload and delete return 405 Method Not Allowed, and list fails to parse the response: with no list route, GET / serves the index document instead of JSON. download works in every mode.

To publish content to a static or spa server, put the files in its storage directory and run stowry populate.

The client is the same binary as the server, so there is nothing extra to install. See Installation.

The client needs three settings: where the server is, and the key pair to sign with.

config.yaml
endpoint: http://localhost:5708
auth:
access_key: YOUR_ACCESS_KEY
secret_key: YOUR_SECRET_KEY

Each is reachable three ways, highest first: a flag, an environment variable, then the config file.

Setting Flag Environment variable Default
Server address -e, --endpoint STOWRY_ENDPOINT http://localhost:5708
Access key -a, --access-key STOWRY_ACCESS_KEY -
Secret key -k, --secret-key STOWRY_SECRET_KEY -
Config files -c, --config STOWRY_CONFIG ./config.yaml
Terminal window
export STOWRY_ENDPOINT=https://storage.example.com
export STOWRY_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
export STOWRY_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

A flag value is visible in the process list, so the environment and the config file are the better places for credentials.

Without credentials the client fails before it makes a request, rather than returning a 401:

Error: no credentials: set auth.access_key and auth.secret_key, or pass --access-key and --secret-key
Terminal window
stowry upload <local-path> [remote-path]

A directory is uploaded whole: its contents go directly under the remote path, with the tree below it preserved. The directory’s own name is not added.

With no remote path, one is derived from the local path by stripping any leading ./, ../ or /.

Flag Description
--content-type Content type for every file uploaded. Defaults to detecting it from each file’s extension.
Terminal window
# Remote path defaults to the local path
stowry upload ./images/photo.jpg # -> images/photo.jpg
# Explicit remote path
stowry upload ./file.txt custom/path.txt
# A directory: contents land under "assets"
stowry upload ./site assets # site/a.css -> assets/a.css
# Override the detected content type
stowry upload ./data.bin archive/data.bin --content-type application/x-custom

Each upload prints the local path, the URL it landed at, its size and its content type.

Terminal window
stowry download <remote-path> [local-path]

With no local path, the file is written to the base name of the remote path. A local path of - writes it to stdout instead.

Terminal window
# Writes ./photo.jpg
stowry download images/photo.jpg
# Writes ./local-copy.jpg
stowry download images/photo.jpg local-copy.jpg
# Straight to stdout
stowry download config/settings.json - | jq .

In static and spa modes the server resolves a missing object to an index or fallback document, so a download may return one of those rather than fail.

Terminal window
stowry list [prefix]

Objects come back oldest first. Results are paged: each page prints the cursor to pass back for the next one.

Flag Description
--limit Objects per page. Defaults to the server’s own limit.
--cursor Continue from a cursor a previous page printed.
--all Follow cursors until every object is listed.
Terminal window
# Everything on the server
stowry list
# Only one prefix
stowry list images/
# One page at a time
stowry list --limit 10
# Every page, however many there are
stowry list images/ --all

Output is a table, followed by the count and total size:

PATH SIZE CONTENT TYPE UPDATED
docs/a.txt 5 text/plain; charset=utf-8 2026-09-18T21:56:17Z
site/a.txt 5 text/plain; charset=utf-8 2026-09-18T21:56:17Z
2 objects, 10 bytes
more: --cursor MjAyNi0wOS0xOFQyMTo1...
Terminal window
stowry delete <remote-path> [remote-path...]

Every path is attempted, so one failure does not abandon the rest. Each outcome is printed, and the command exits non-zero if any path failed.

Terminal window
# One object
stowry delete images/photo.jpg
# Several at once
stowry delete old/a.txt old/b.txt old/c.txt
deleted old/a.txt
failed old/missing.txt: server error: 404 - Object not found
Error: delete 1 of 2: old/missing.txt: server error: 404 - Object not found
Code Meaning
0 Success
1 Error: missing credentials, authentication failed, object not found, server error
Terminal window
export STOWRY_ENDPOINT=http://localhost:5708
export STOWRY_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
export STOWRY_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
stowry upload ./report.pdf documents/report.pdf
stowry list documents/
stowry download documents/report.pdf
stowry delete documents/report.pdf
Terminal window
# Every file under ./dist lands under "site"
stowry upload ./dist site
# Check what arrived
stowry list site/ --all

There are no stored profiles. Point the client at a server per invocation, or keep one config file per environment:

Terminal window
# Per invocation
stowry --endpoint https://staging.example.com list
# Per config file
stowry -c ./staging.yaml list
stowry -c ./production.yaml list

--config takes several files, merged left to right, so shared settings can live in one file and the endpoint in another:

Terminal window
stowry -c ./common.yaml,./staging.yaml list