Build

Storage

Files live in buckets inside your app. Each bucket has a read and a write policy, like a table, plus optional size and type limits. Uploads are one raw PUT.

Buckets

FieldValues
readpublic, authenticated, owner, secret
writeauthenticated, owner, secret
max_file_sizebytes, up to the 50 MB platform limit
allowed_typescontent types, wildcards allowed, for example ["image/*"]

With owner, a signed-in user may only use keys that start with their user id and a slash, such as <user id>/avatar.png. The secret key may use any key.

Public avatars that only their owner can replace:

berth storage buckets create --app "$APP" avatars --read public --write owner --max-file-size 2000000

Private documents for your server only, with a type allow list:

curl -sS -X POST https://api.atberth.com/v1/apps/$APP/storage/buckets \
  -H "Authorization: Bearer $SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"docs","read":"secret","write":"secret","allowed_types":["text/plain","application/pdf"]}'
berth storage buckets list --app "$APP"

Upload

Upload is PUT /storage/objects/{bucket}/{key} with the raw file as the body. The Content-Type you send is stored and served back. A second PUT to the same key overwrites it.

Sign up a user and upload their avatar as them:

SESSION=$(curl -sS -X POST https://api.atberth.com/v1/apps/$APP/auth/signup \
  -H "Authorization: Bearer $PUBLISHABLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"ada@example.com","password":"correct horse battery"}')
ACCESS_TOKEN=$(echo "$SESSION" | jq -er .access_token)
USER_ID=$(echo "$SESSION" | jq -er .user.id)
echo 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==' | base64 -d > avatar.png
curl -sS -X PUT https://api.atberth.com/v1/apps/$APP/storage/objects/avatars/$USER_ID/avatar.png \
  -H "apikey: $PUBLISHABLE_KEY" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: image/png" \
  --data-binary @avatar.png

Outside her own folder, the owner policy refuses with 403 policy_violation:

curl -sS -X PUT https://api.atberth.com/v1/apps/$APP/storage/objects/avatars/someone-else/avatar.png \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: image/png" \
  --data-binary @avatar.png

From the CLI (secret key), the key defaults to the file name:

echo "Berth storage example" > readme.txt
berth storage upload --app "$APP" docs readme.txt
berth storage upload --app "$APP" docs readme.txt notes/readme.txt --content-type text/plain

List

berth storage ls --app "$APP" docs --prefix notes/
curl -sS "https://api.atberth.com/v1/apps/$APP/storage/objects/avatars?prefix=$USER_ID/&limit=100" \
  -H "Authorization: Bearer $SECRET_KEY"

Download

A public bucket needs no key at all, so the URL can go straight into an image tag. Add ?download=1 to get an attachment.

curl -sS -o downloaded.png https://api.atberth.com/v1/apps/$APP/storage/objects/avatars/$USER_ID/avatar.png
file downloaded.png
berth storage download --app "$APP" docs readme.txt -o copy.txt
cat copy.txt

Signed URLs

For a private bucket, hand out a link that works without a key until it expires. Anyone allowed to read the object may sign it.

SIGNED_URL=$(curl -sS -X POST https://api.atberth.com/v1/apps/$APP/storage/sign/docs/readme.txt \
  -H "Authorization: Bearer $SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expires_in":600}' | jq -er .url)
curl -sS "$SIGNED_URL"
berth storage sign --app "$APP" docs readme.txt --expires 3600

The link carries ?expires= and ?signature=. Changing either one breaks it.

Delete

berth storage rm --app "$APP" docs notes/readme.txt
curl -sS -X DELETE https://api.atberth.com/v1/apps/$APP/storage/objects/avatars/$USER_ID/avatar.png \
  -H "Authorization: Bearer $ACCESS_TOKEN"

A bucket must be empty before it can be deleted, unless you pass --force (?force=true), which deletes its files too.

berth storage buckets delete --app "$APP" docs --force
berth storage buckets delete --app "$APP" avatars