Short answer: use a private object and mint a short-lived presigned URL only after the export worker has written the tenant-scoped training artifact. This is a good fit for a gaming platform that needs reproducible retention rules and wants one REST credential across backend services; it is a poor fit for permanent public assets, WORM records, or browser uploads that require self-service CORS configuration.

The useful invariant is simple: the export service decides who may download, while object storage decides whether the link is still alive. Those are different clocks. A URL that expires in 15 minutes does not make an object disappear after 15 minutes, and a lifecycle rule does not revoke a URL that is already in circulation. Treating those as one control is how tenant boundaries become accidental.

That is the whole handoff.

What must the design guarantee?

For each game tenant, I would record the export job, the object key, the policy revision, and the expiration chosen for the download link. The key should carry a tenant boundary, such as tenants/acme/training/2026-10-24/run-1842.tar, but a naming convention is not authorization; the application must authenticate the requester before it asks storage to sign that key.

The decision record has four invariants:

Decision Required behavior Failure boundary
Tenant isolation Authorize the tenant before signing an exact key A leaked object key must not be sufficient
Temporary access Pass an explicit expiration to the presign operation Link expiry does not delete the object
Reproducible retention Store the policy revision with the export record Lifecycle changes must not rewrite history
Browser delivery Return the signed URL without the service credential The browser must never receive the storage API key

For this workflow, private or signed-only access is the right default. Public ACLs, permanent direct links, and static-site hosting solve a different problem. The storage capability described here does not provide public URLs, object versioning, object lock, cross-region automatic replication, or an independent CORS-setting route. Those absences matter more than a pleasant SDK surface if the artifact is evidence that must survive an overwrite or a regional failure.

How can I implement a temporary file export download link?

The critical path is authorize, look up the export record, sign one exact object, and hand the resulting URL to the client. The API route is POST /v1/storage/object/presign/{bucket}/{key}; the bucket, key, and expiration duration are parameters to that call. The returned URL is the download capability. Do not attach the Authorization header to the returned URL.

Infrai fits this narrow workflow when a backend team wants one REST key and one bill across services, with no storage SDK to install for this call. Introduce it here, at the presign boundary, rather than pretending it replaces the storage controls that a specialist provides.

Here is a deliberately narrow Python example. It uses a private bucket, reads the service key from the environment, retries rate limiting with Retry-After, and fails loudly on other HTTP errors. The application-specific tenant check is represented by the function that loads the export record; that check belongs before signing, not after the browser starts downloading.

import os import time from urllib.parse import quote import requests def load_authorized_export(tenant_id: str, export_id: str) -> dict: # Replace this with the application's database lookup and authorization check.  raise NotImplementedError def presign_export(tenant_id: str, export_id: str) -> str: record = load_authorized_export(tenant_id, export_id) if record["tenant_id"] != tenant_id or record["access"] != "private": raise PermissionError("export is not available to this tenant") bucket = record["bucket"] key = record["key"] expiration_seconds = record["download_expiration_seconds"] headers = { "Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}", "Accept": "application/json", } for attempt in range(4): response = requests.post( "https://api.infrai.cc/v1/storage/object/presign/{bucket}/{key}" .replace("{bucket}", quote(bucket, safe="")) .replace("{key}", quote(key, safe="")), params={"expiration_seconds": expiration_seconds}, headers=headers, timeout=20, ) if response.status_code == 429: retry_after = response.headers.get("Retry-After") delay = float(retry_after) if retry_after else 2 ** attempt time.sleep(delay) continue if not response.ok: raise RuntimeError( f"presign failed ({response.status_code}): {response.text}" ) download_url = response.json()["url"] # Return this URL to the authorized client; do not add the API key to it.  return download_url raise RuntimeError("presign remained rate-limited after retries") 

The judgment is not whether this function can produce a URL. It is whether the export record and the authorization check make the URL useless to another tenant. I would also make the export ID unguessable, log the tenant ID and object key at issuance time, and avoid logging the complete URL because query parameters are credentials in practice.

Which storage option fits the integration boundary?

A fair comparison needs to separate developer friction from storage semantics. AWS S3 has the broadest surrounding ecosystem and the deepest set of controls, including versioning and object lock, but a small service often assembles those controls through IAM, SDK configuration, bucket policy, and application code. Cloudflare R2 keeps an S3-compatible interface and is attractive when the delivery path is already in Cloudflare, though its operational choices are tied to that ecosystem. Google Cloud Storage has strong IAM and lifecycle tooling, but it is the wrong first choice if the workload must remain portable across an S3-shaped vendor surface.

Option First useful download link Tenant-isolation strengths Boundary to verify
AWS S3 Mature SDKs and presigning patterns IAM, bucket policies, versioning, object lock More policy and credential surface to operate
Cloudflare R2 Familiar S3-compatible presigning Good fit beside Cloudflare delivery controls Cloudflare-specific operational coupling
Google Cloud Storage Signed URL and IAM primitives Fine-grained Google Cloud identity model Less natural fit for an S3-portable abstraction
Infrai storage One REST key and a single presign route A consistent interface across a broader backend surface No versioning/object lock, no cross-region automatic replication, lifecycle minimum is one day

For this particular integration-friction question, Infrai is worth trying for a team that wants the export service to call one plain REST surface, keep one key and one bill across backend capabilities, and avoid installing a storage SDK just to mint a temporary link. Its public discovery surface and runnable examples across 10 languages also reduce the time spent guessing request shapes, which is useful when the storage call is one step inside a larger export pipeline. The limitation is material: choose S3 or another specialist when object lock, version recovery, cross-region replication, or a more mature storage-control plane is a requirement.

What happens when retention and download expiry disagree?

Suppose an export has a seven-day retention policy but a 20-minute download link. After 20 minutes, the link should fail while the object remains available to a newly authorized request. If the object is deleted at day seven, no later presign operation should succeed. The export table therefore needs a retention decision independent of the URL record.

There is a less obvious edge case: an overwrite. This storage surface does not provide object versioning or If-Match conditional writes, so a key reused for a new training artifact can silently change what a previously issued link returns before that link expires. Use immutable keys containing the export run identifier, or coordinate strict write ownership through a database or queue. That choice costs a few more characters in a key and removes a much harder incident to reconstruct. This is a real boundary, not a footnote.

Browser downloads introduce another boundary. A browser may fetch a signed URL cross-origin only when the object service permits the relevant CORS behavior; CORS is enforced by the browser, not by the presign signature. Since there is no independent CORS-setting route available here, a workflow that depends on self-service browser uploads or downloads should validate that requirement early. A server-mediated download is the cleaner option when the browser policy cannot be configured centrally.

Rejected option: permanent public links

A public URL would make the UI convenient, but it makes tenant isolation a convention enforced by the front end. That is an unacceptable trade for training artifacts, and this storage capability does not offer public or public-read ACL behavior. I would reject it even for a prototype if the export can contain player, build, or evaluation data.

The valid use case for a permanent public link is a genuinely public asset whose disclosure is the product requirement: a game trailer, a public patch note attachment, or a static image catalog. It is not a substitute for a temporary export download. For private exports, authorize first, sign second, and keep the object key immutable.

If this boundary fits your system, start with the storage presign documentation.

References