Skip to main content
mc.storage is a thin wrapper around your private S3 storage area with two well-known directories:
  • Incoming/ — files you send in.
  • Outgoing/ — files placed for you to read.
upload* defaults to Incoming/; download*, list*, and delete default to Outgoing/ so the natural list-then-act loop targets the same direction. Override per call with direction="incoming" / direction="outgoing".
pip install "minerva-sdk[storage]"
from minerva import Minerva

mc = Minerva()  # MINERVA_API_KEY from env

Single file

# Upload — defaults to Incoming/
mc.storage.upload("data/customers.csv")
# → Incoming/customers.csv

mc.storage.upload("data/customers.csv", remote_name="batch-001.csv")
# → Incoming/batch-001.csv

mc.storage.upload("data/customers.csv", subdir="2026-06-29")
# → Incoming/2026-06-29/customers.csv

# Download — defaults to Outgoing/
mc.storage.download("results.csv")
# → ./results.csv

mc.storage.download("sub/inner.csv", local_path="out/inner.csv")
# → ./out/inner.csv  (creates parent dirs)

# Delete — defaults to Outgoing/. Pass direction="incoming" to clean up your own uploads.
mc.storage.delete("results.csv")
mc.storage.delete("batch-001.csv", direction="incoming")
upload returns the relative remote key. download returns the local Path that was written.

Listing

for obj in mc.storage.list():           # Outgoing/ by default
    print(obj.key, obj.size, obj.last_modified)

for obj in mc.storage.list(subdir="2026-06"):
    ...
Each ObjectMeta carries key (relative — pass back to download / delete as-is), size, last_modified, and etag.

Bulk

Bulk methods run uploads/downloads in parallel — defaults to 8 workers, tune with max_concurrency=. Order is preserved; the first error in a batch fails the call.
# Many files at once
mc.storage.upload_many(["a.csv", "b.csv", "c.csv"])

# Whole directory tree, relative paths preserved
mc.storage.upload_directory("./batch-2026-06-29")
# Sends ./batch-2026-06-29/x.csv     → Incoming/x.csv
# Sends ./batch-2026-06-29/sub/y.csv → Incoming/sub/y.csv

mc.storage.upload_directory("./day", subdir="2026-06-29")
# → Incoming/2026-06-29/...

# Pull a batch by keys (typically the output of list())
mc.storage.download_many(
    ["results-1.csv", "sub/inner.csv"],
    local_dir="./out",
)

# List, then download everything — single call
mc.storage.download_all(local_dir="./out")

Credentials

Storage credentials are fetched lazily on first call and cached on the client. The SDK auto-refreshes them when they’re within ~2 minutes of expiry, so a long-running process can hold one Minerva() instance and keep working across hour-long sessions without manual renewal. Construct one Minerva() per process and reuse it — every fresh instance pays the credential round-trip again.
creds = mc.storage.get_credentials()
print(creds.bucket_name, creds.prefix, creds.aws_session_expiration)
# Secrets (aws_secret_access_key, aws_session_token) are hidden from repr
# but accessible by attribute.

Access scope

Storage credentials are scoped to your org’s directory — the underlying session policy only permits s3:GetObject / s3:PutObject / s3:DeleteObject on {your_org}/* keys, and s3:ListBucket filtered to the same prefix. Other orgs’ data is unreachable regardless of what you pass to the SDK. The SDK additionally rejects path-traversal-shaped keys (.. segments, absolute paths, backslashes) at the call site for clean local errors — but the real isolation is server-side.

Errors

All storage errors derive from MinervaStorageError:
ConditionRaises
[storage] extra not installed (boto3 missing)MinervaStorageExtraNotInstalledError
Credentials endpoint returned an unexpected shape, missing fields, or unparsable expiryMinervaStorageCredentialError
Object doesn’t exist (404)MinervaStorageObjectNotFoundError — carries .key
Access denied (403)MinervaStorageAccessDeniedError — carries .key
Network / transient S3 failure (5xx, timeouts, …)MinervaStorageTransferError — carries .original
Bad input (non-file path, .. in key, invalid direction, …)MinervaValidationError
from minerva import (
    MinervaStorageError,
    MinervaStorageObjectNotFoundError,
    MinervaStorageAccessDeniedError,
    MinervaStorageTransferError,
)

try:
    mc.storage.download("missing.csv")
except MinervaStorageObjectNotFoundError as e:
    print(f"no such key: {e.key}")
except MinervaStorageError:
    raise
The [storage] extra pulls in boto3 so the base wheel stays small. If you already have boto3 in your environment, the extra still wires up the SDK’s lazy-import guard cleanly — install it either way.