Storage

Store files in buckets. Buckets and files use the same permission model as collections and documents.

Create a bucket & upload

curl
curl -X POST "https://base.finiteskills.com/v1/storage/buckets" -H "X-Appwrite-Project: <YOUR_PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" -H "Content-Type: application/json" \
  -d '{"bucketId":"media","name":"Media","permissions":["read(\"any\")","create(\"users\")"],
       "maximumFileSize":30000000,"allowedFileExtensions":["png","jpg","webp"]}'

# upload (multipart). fileId "unique()" auto-generates an id.
curl -X POST "https://base.finiteskills.com/v1/storage/buckets/media/files" \
  -H "X-Appwrite-Project: <YOUR_PROJECT_ID>" -H "X-Appwrite-Key: <API_KEY>" \
  -F "fileId=unique()" -F "file=@./photo.png"

Large files upload in 5 MB chunks with a Content-Range header and the same fileId reused across requests — the official SDKs do this automatically. When a bucket declares allowedFileExtensions, uploads whose name falls outside the allowlist are rejected (an empty list allows any extension).

Serve, transform & sign

Endpoints
GET  /storage/buckets/media/files/{id}/view      # inline
GET  /storage/buckets/media/files/{id}/download  # attachment
GET  /storage/buckets/media/files/{id}/preview?width=400&height=300&quality=80&output=webp
POST /storage/buckets/media/files/{id}/signed-url { ttlSeconds }  # time-limited public URL

When a CDN is configured for the deployment, files are mirrored to it and preview transforms are served, cached, from the CDN; the file's metadata then includes a cdnUrl. Signed URLs need no auth and expire after their TTL. Each file's metadata also carries a signature — the MD5 of its bytes — so a client can detect changes or verify an upload.

Range requests (partial content)

The view, download and signed endpoints honour the HTTP Range header and advertise Accept-Ranges: bytes. A ranged request returns 206 Partial Content with a Content-Range — this is what lets a browser seek within a video or resume a download.

Range
curl -r 0-1048575 "https://base.finiteskills.com/v1/storage/buckets/media/files/clip/view" \
  -H "X-Appwrite-Project: <YOUR_PROJECT_ID>"      # -> 206, first 1 MiB

Signed direct uploads

Hand a client a one-time URL so it can upload straight to storage without a project key. Your server mints the URL; the client then PUTs the raw bytes to it:

Two steps
# 1) server mints the URL (the signature is the authorization)
curl -X POST "https://base.finiteskills.com/v1/storage/buckets/media/files/unique()/signed-upload-url" \
  -H "X-Appwrite-Project: <YOUR_PROJECT_ID>" -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" -d '{"ttlSeconds":600}'
#   -> { "url": ".../signed-upload?...&signature=...", "expires": ... }

# 2) client PUTs the bytes (no project key needed); ?name= sets the stored filename
curl -X PUT "<url>&name=photo.png" \
  -H "Content-Type: application/octet-stream" --data-binary @./photo.png

The signed PUT still enforces the bucket's maximumFileSize and allowedFileExtensions, and returns 401 on a bad or expired signature.