Objects

Objects are :storage.Object nodes uniquely identified by (bucket_id, path). The path is a flat string but the conventional /-separator lets clients render a folder tree on top.

Upload

Two surfaces: POST creates (409 on conflict), PUT upserts. Body is the raw bytes; Content-Type sets the stored MIME.

# Single-shot create
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: image/png" \
  --data-binary @alice.png \
  http://localhost:7474/storage/v1/object/avatars/alice.png

# Upsert (replaces if it exists)
curl -X PUT ...same headers... \
  http://localhost:7474/storage/v1/object/avatars/alice.png

For files larger than the configured upload_chunk_size (default 5 MiB) use the TUS resumable endpoint - see the Resumable Uploads page.

Download

Authenticated downloads via GET /storage/v1/object/{bucket}/{path}; public buckets also expose GET /storage/v1/object/public/{bucket}/{path}. Both honour Range headers so video and audio players can seek.

HEAD /storage/v1/object/{bucket}/{path} returns the metadata headers (Content-Length, Content-Type, ETag, X-Content-SHA256) without streaming the body - ideal for existence checks.

Copy / move

# Copy across buckets
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "source_bucket": "avatars",
    "source_path": "alice.png",
    "dest_bucket": "thumbnails",
    "dest_path": "alice.png"
  }' \
  http://localhost:7474/storage/v1/object/copy

# Rename within a bucket
curl -X POST .../storage/v1/object/move -d '{
  "source_bucket": "avatars", "source_path": "old.png",
  "dest_bucket": "avatars",  "dest_path": "new.png"
}'

List

POST /storage/v1/object/list/{bucket} with an optional prefix, limit, offset, and sort. Defaults to limit: 100, capped at 1000.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"prefix":"users/","limit":50,"sort_by":"created_at","order":"desc"}' \
  http://localhost:7474/storage/v1/object/list/avatars

Delete

DELETE /storage/v1/object/{bucket}/{path}. The backend blob is released automatically when the last :storage.Object referencing its content hash is removed (refcount cleanup).