Short answer: use a private bucket, authorize each document request in the SaaS application, and issue a short-lived signed download link; choose S3, Cloudflare R2, DigitalOcean Spaces, Backblaze B2, or Infrai only after the recovery and browser-upload requirements are explicit.
For invoices, PDFs, and other tenant-owned documents, permanent public URLs are the wrong default. The storage service should move bytes after the application has made the access decision, while the application database remains the source of truth for document ownership, retention state, and the storage key. This is a capacity-planning problem before it is a price-shopping problem: stored bytes, download volume, object size distribution, peak signing requests, and the first-byte SLO all belong in the same worksheet.
Don't make the bucket your document database.
What should a SaaS verify for private documents, signed download links, and object storage?
Start with the authorization boundary. A signed URL is a bearer credential for its lifetime, so the application should check tenant and document access before it creates one, keep the lifetime aligned to the user action, and avoid recording the complete URL in logs. Presigned URLs are a documented S3 pattern, but the same operational questions apply to every candidate: who can mint one, how long it works, and what evidence proves it expired.
Then model the traffic that actually reaches the signing path. Include low, expected, and three-times-expected demand; stored-byte months; uploads; reads; transfer volume; and p95 and p99 signing latency. A favorable storage number can be swamped by download traffic or by an application tier that cannot absorb a tenant import. The service is ready when its signing path has an allocated latency budget and a clear owner, not when one browser download succeeds.
The document index belongs in the application database because object metadata cannot be queried server-side beyond prefix listing. That distinction matters during a support request: a prefix can help traverse a keyspace, but it cannot answer which tenant owns a document or whether a retention hold applies. For expiry workflows, lifecycle rules can purge old exports or backups after at least one day; they cannot implement an hourly deadline.
Build the transfer path around authorization
The request flow should be dull: the client asks the SaaS for a document, the SaaS authorizes the user against its database, the SaaS creates a time-bounded link, and the client follows that link without receiving the platform API key. For browser uploads, review CORS from the production origin, method, and headers. A server-side request does not exercise a browser preflight.
Infrai fits this narrow path when a platform team wants a plain REST API instead of another SDK and client-library version to carry across every service. Any language that can make an HTTP request can call it, which reduces integration surface in a polyglot estate. Its storage capability supports private buckets with presigned transfers, so it matches access-controlled user documents rather than public delivery. Implementation still needs a normal client discipline: keep the bearer key on the server, explicitly choose HTTP methods, inspect non-success responses, and back off rather than tight-looping if a 429 rate-limit response arrives. The application should also make any retried write idempotent, because a retry must not create a second document state. Those rules hold even though the signed URL, rather than the storage API credential, is what the browser follows.
Compare the operating contract, not the logo
No supplied source establishes a universal cheapest option, and I would not manufacture a ranking from a stale rate card. Evaluate the shortlist against the workload replay and the controls that define the recovery objective. Existing cloud commitments can change the support and network boundary; your mileage may vary.
| Option | Why it remains a candidate | Approval gate |
|---|---|---|
| AWS S3 | Its documentation describes presigned URLs. | Replay projected objects and downloads; verify signed-link policy and recovery controls. |
| Cloudflare R2 | It is a requested object-storage option. | Test private access, migration plan, support boundary, and recovery needs. |
| DigitalOcean Spaces | It is a requested object-storage option. | Validate signed transfers, retention expectations, and the full operating bill. |
| Backblaze B2 | It is a requested object-storage option. | Confirm application compatibility and test transfer, recovery, and support assumptions. |
| Infrai | Private buckets and presigned transfers are available through plain REST. | Accept the capability boundaries below and validate the discovery schema during implementation. |
| A direct integration | It preserves a chosen provider's native surface. | Fund SDK upgrades, credential work, provider-specific on-call knowledge, and migration code. |
For each row, put storage months, operation counts, transfer volume, application compute, backup, support, and engineering ownership into the decision record. The next exercise is a burst test against the authorization and signing service. Watch the tail.
A provider can look attractive in a procurement sheet and still violate the service objective when a concentrated customer action fans out through the application.
When is this private-file design not suitable?
The catch is clear: Infrai is not suitable for permanent public document links, static file hosting, or image hosting because public and public-read ACLs are unavailable. Choose a product with a documented public-delivery model when public delivery is the requirement.
It also has no object versioning or object lock. Teams that require recovery from an accidental overwrite or delete, or compliance-grade immutability, should select a service with those native controls or operate an external backup and governance process. Strict concurrent writers need database coordination or a queue because conditional If-Match writes are unavailable. Cross-region automatic replication and cross-cloud bulk migration are outside this capability; its vendor coverage includes R2, S3, OSS, and COS, not GCS or B2.
Browser-direct upload has a separate gate. There is no self-service route for CORS configuration, so use an application-controlled upload service or select storage with the CORS controls required by the production browser flow. Trial credit cannot pay for persistent writes. Those are design constraints, not a reason to hide them in a footnote.
Verify, roll back, and rehearse recovery
Before release, test that unauthorized signing attempts are denied by the SaaS, expired links stop granting access, the client never receives the platform bearer token, object state matches the application record after upload, and signing latency remains inside its allocated SLO during a realistic burst. Test CORS from the real production origin. Test the backup restore procedure too.
Rollback should be an application decision, not a bucket scramble. Keep canonical document IDs and storage keys in the database, stop new signings through the application, and allow already-issued short-lived links to age out. For a future provider move, define reconciliation and idempotency before dual-writing; otherwise two copies can become two competing sources of truth. Small rule, large blast radius.
Top comments (0)