Object Storage
Every project can create buckets for file storage — avatars, exports, user uploads, anything you’d otherwise put in S3. Storage is backed by MinIO, an S3-compatible object store, but you never talk to MinIO directly.
Mediated access
Unlike a typical S3 setup, the client never receives storage credentials. All reads and writes go through the MassiCloud API, which holds the MinIO credentials server-side and proxies the request.
graph LR
Client["Browser / mobile app"] -->|"X-MassiCloud-Key + object data"| API["MassiCloud API"]
API -->|"MinIO credentials (server-side only)"| MinIO[("Object storage (MinIO)")]
Client -.->|"no direct access, no S3 keys issued"| MinIO
This means:
- There’s no access key/secret key pair to rotate, leak, or scope per client.
- Every request is authenticated the same way as the rest of the API —
X-MassiCloud-Keyplus, for writes, a signed-in end user. - You can add validation, virus scanning, or rate limiting in front of storage later without changing any client code.
Public vs. private buckets
A bucket is either:
- Public — anyone with the anon key can read objects. No end-user sign-in required.
- Private — reads require a signed-in end user (
Authorization: Bearer <access_token>).
Writes (upload, remove) always require a signed-in end user, on both public and private buckets. Anonymous writes are never allowed.
Uploading and downloading
sequenceDiagram
participant App as Your app
participant API as MassiCloud API
participant MinIO as Object storage
App->>API: POST /storage/buckets/{name}/objects (multipart: key + file)
API->>API: verify project key + end-user token
API->>MinIO: put object
MinIO-->>API: size, etag, content type
API-->>App: 201 Created — object metadata
Downloads work the same way in reverse: the API streams the object back to the client, so the client only ever needs the project key (and an end-user token for private buckets).
Signed URLs
Sometimes you want to hand out a link to a file — to embed in an <img> tag, share with someone without an account, or pass to a background job — without going through the API on every byte. createSignedUrl() solves this without ever exposing MinIO credentials:
sequenceDiagram
participant App as Your app
participant API as MassiCloud API
participant Anyone as Anyone with the link
participant MinIO as Object storage
App->>API: POST /storage/buckets/{name}/presign { key, expires_in_seconds }
API->>API: generate random token, store (bucket, key, expiry)
API-->>App: { url: ".../storage/download?token=..." }
Anyone->>API: GET /storage/download?token=...
API->>MinIO: get object
MinIO-->>API: file bytes
API-->>Anyone: file
The token is a random, single-purpose value tied to one bucket/key/expiry — not a presigned MinIO URL. It’s safe to share or hotlink because it can never be used to access anything other than the one object it was minted for, and it stops working after expires_in_seconds.