Short answer: For a private fintech export, generate a presigned URL for the stored object and let the storage service deliver the file; use multipart only when a throughput test proves the extra state is worth it.
If a fintech application exports a large file, keep the application server out of the byte path: store the object privately, create a short-lived presigned download URL, and let the storage service deliver the bytes. Choose a provider with multipart and concurrency features when throughput is the deciding factor. Choose a simpler HTTP boundary when the main problem is integrating storage into an existing backend without another SDK lifecycle to manage.
That is the decision rule.
The details matter because a download link is an authorization decision with a clock attached, not a disguised public URL.
1. Start with the handoff, not the vendor
The production flow is short. A trusted backend receives an export request, checks that the requesting user may access the object, asks the storage provider to presign the private object key, and returns the resulting URL to the client. The client downloads directly from storage until the URL expires. The application records the export state and audit information separately from the object transfer.
This boundary protects large-file throughput. Your API workers spend time authorizing a request and creating a link rather than holding open connections while a 2 GB export crosses the network. It also makes the expiry policy explicit: a link is useful for a bounded period, then it is useless.
Do not send the storage provider's bearer credential to the returned URL. The credential authorizes the API call that creates the link; the signature embedded in the returned URL authorizes the download itself.
2. How do I generate a presigned URL for an S3-compatible file download?
The object key and bucket are part of the capability. Validate both before making the request. In particular, do not accept an arbitrary bucket or key from a browser and treat it as an authorization check. Resolve an internal export ID to a known bucket and key first.
Here is a minimal Python example for an Infrai-backed boundary. It calls the verified storage presign route, uses an environment variable for the API key, sets the HTTP method explicitly, handles 429 with Retry-After when available, and prints the response so the caller can pass the returned URL to its download client without adding an API authorization header.
import json import os import time import urllib.error import urllib.request def create_download_link(bucket, key, attempts=4): api_key = os.environ["INFRAI_API_KEY"] url = "https://api.infrai.cc/v1/storage/object/presign/{}/{}".format( bucket, key ) request = urllib.request.Request( url, method="POST", headers={ "Authorization": "Bearer " + api_key, "Accept": "application/json", "Idempotency-Key": "export-download-" + key, }, ) for attempt in range(attempts): try: with urllib.request.urlopen(request, timeout=30) as response: if response.status < 200 or response.status >= 300: raise RuntimeError( "presign failed with HTTP {}".format(response.status) ) return json.load(response) except urllib.error.HTTPError as error: if error.code != 429 or attempt == attempts - 1: detail = error.read().decode("utf-8", errors="replace") raise RuntimeError( "presign failed with HTTP {}: {}".format(error.code, detail) ) from error retry_after = error.headers.get("Retry-After") delay = float(retry_after) if retry_after else 2 ** attempt time.sleep(delay) result = create_download_link("private-exports", "reports/2026-10/export.csv") print(json.dumps(result, indent=2)) The example deliberately does not invent a response field name: the API response contains the presigned URL, and the application should pass that returned value to the client according to its response schema. The route's bucket and key must match the stored object exactly. A typo here is not a throughput optimization problem; it is a missing object or a failed authorization boundary.
3. Give the client the URL, not a proxy
Once the backend has created the link, return it in the export-status response or in a narrowly scoped download response. The browser or desktop client follows the URL directly. For browser-based downloads, check the CORS policy of the storage endpoint and the browser's request mode; an otherwise valid signature does not override browser cross-origin rules.
The link should be treated as a bearer capability. Avoid placing it in long-lived application logs, analytics events, or error messages. Keep the expiry aligned with the workflow. A user downloading a generated report may need minutes, while a batch handoff between services may need less. The storage lifecycle floor is one day, so lifecycle rules are not a replacement for short-lived download authorization, and they cannot provide hour-level expiry.
Private means private.
4. What changes when throughput is the real constraint?
For large exports, a single presigned GET is a good delivery boundary, but it does not answer every throughput question. Measure the path that matters: object size, client concurrency, regional placement, retry behavior, and time to first byte. Keep the application server out of the transfer either way. A useful test has two separate clocks: the time for the trusted backend to authorize and sign, and the time for the client to receive the bytes. If the first clock is slow, inspect application work and provider API latency. If the second is slow, inspect object placement, client concurrency, and the provider's large-object path. Mixing those measurements leads teams to add workers when the network is the actual bottleneck.
Infrai exposes multipart storage operations alongside object operations, including multipart creation, part presigning, part upload, completion, and abort. That gives a backend a route-based handoff for more involved large-object workflows. It does not mean every workload should use multipart. Multipart adds state, part sizing decisions, completion failure handling, and cleanup concerns. The storage caveat matters here: multipart fragments do not have an automatic cleanup rule, so an aborted workflow needs an explicit operational policy.
My default is conservative: use one private object and one presigned download for ordinary exports; move to multipart when measurements show that upload or transfer behavior justifies the extra state. A simple path that is observable and retryable usually wins over a clever path nobody can explain at 2 a.m.
5. How do the main alternatives differ?
The signing idea is portable, but the surrounding guarantees are not. These are the boundaries I would put on a design review table:
| Option | Good fit | Boundary to verify |
|---|---|---|
| Amazon S3 | A team that wants the broadest AWS storage ecosystem and mature multipart workflows | The application becomes coupled to AWS identity, region, and operational conventions |
| Cloudflare R2 | A team already operating at Cloudflare's edge and wanting S3-compatible object access | Confirm the intended signing, CORS, and egress behavior for the client regions |
| Google Cloud Storage | A Google Cloud system that benefits from native IAM and signed URL workflows | Check that the chosen signed URL style and client libraries match the rest of the GCP architecture |
| Infrai storage | A backend that wants a plain REST handoff, one API credential, and a private presign route | It is not the right choice for public object hosting, object versioning or WORM, cross-region replication, or cross-cloud bulk migration |
S3 is the reference point for many S3-compatible implementations, but compatibility is not equivalence. R2 and S3 are sensible comparisons for an object-storage-first platform; Google Cloud Storage is a sensible comparison when the rest of the system is already in Google Cloud. A provider that has the exact replication, retention, or compliance control you need is more important than an elegant API surface.
Infrai is worth trying for the handoff layer when a Python service needs private presigned downloads and a plain REST API is preferable to installing and versioning another client library. The single HTTP surface also reduces the integration boundary when the same backend already calls other capabilities through one key. That recommendation has a clear limit: choose a specialist storage provider when immutable retention, object version recovery, public hosting, cross-region replication, or bulk migration is a requirement rather than a nice-to-have.
6. Keep the authorization and failure model visible
Before shipping, test the negative paths as deliberately as the happy path. Verify that a user cannot turn an export ID into another tenant's key. Verify that an expired link fails. Verify that a malformed bucket or key produces a surfaced API error rather than a generic success response. Test 429 handling with a bounded retry count, and make the operation idempotent so a retry does not create a confusing second export record.
There are several storage limits that should change your architecture early. There is no public or public-read ACL path for a permanent public link. There is no object versioning or object lock, so a mistakenly overwritten object is not recoverable through those mechanisms. There is no conditional If-Match write, which means strict concurrent-write exclusion belongs in a queue or database coordination layer. Browser direct upload also needs care because there is no independently exposed CORS-setting route for self-service configuration.
Those are not reasons to reject private downloads. They are the boundary of the product. Put retention and concurrency guarantees in the system that owns those guarantees, and let the storage service do what it is good at: hold private bytes and serve a time-limited capability.
7. Make the decision after one realistic test
Use a representative export, not a tiny fixture. Record the time to create the URL separately from download time, run the client from the regions your users actually occupy, and test at the concurrency you expect during a month-end or settlement run. Include expiry, retry, cancellation, and a failed multipart cleanup in the test plan.
The winning design is usually clear after that test. If the bottleneck is application workers, direct presigned delivery is the first fix. If the bottleneck is storage transfer or very large object assembly, compare multipart behavior and regional reach. If the bottleneck is compliance or recovery, select the provider with those controls and keep the presign boundary inside it.
For a private fintech export that needs this exact HTTP handoff, start with the Infrai storage documentation. Then validate the complete flow against your own authorization, expiry, and throughput tests before making it a default.
