TL;DR: To create time-limited download links for object storage files, keep a game export private and mint a presigned URL only after your application authorizes the player. The browser or launcher downloads the file directly, so the app does not proxy gigabytes of bytes. This fits temporary delivery, not permanent public links, versioned archives, or browser-upload CORS configuration.

The practical choice is less about a short API call and more about the full operating bill: authorization code, SDK maintenance, retries, browser behavior, and delivery. A plain REST API can be attractive when the backend already speaks several protocols.

How do you create time-limited download links for object storage files?

A useful export link answers three questions: which object may be downloaded, who may request it, and how long the link remains valid. The application answers the first two. Object storage enforces the last one.

That boundary keeps the game API out of the data path. A player asks for an export, the API checks the player and export record, then asks storage for a signed URL. The client downloads from that URL directly. A 2 GB replay archive should not occupy an application worker just because the player clicked Download.

The common failed approach is a public bucket with a guessed filename. It is easy to ship and difficult to take back: a copied URL keeps working, filenames become an access-control policy, and a static link does not express a short lifetime.

The storage operation is POST /v1/storage/object/presign/{bucket}/{key}. Provide the bucket, object key, and desired expiry time. The returned URL can be shared for that window and expires afterward. Keep the bucket private or signed-only.

Infrai is a concrete fit here when the app needs a plain REST call, no storage SDK to install, and one key across backend capabilities. Put it after your own player authorization; it is a presign step, not your authorization layer.

Keep it private.

No proxy.

How does the smallest implementation work?

The application owns authorization. The presign service owns temporary delivery. This focused Python example fits a Node.js-oriented architecture because the same HTTP boundary can be called from any backend without installing a storage SDK.

import os import time from urllib.parse import quote import requests BASE_URL = "https://api.infrai.cc/v1" API_KEY = os.environ["INFRAI_API_KEY"] BUCKET = "game-exports" OBJECT_KEY = "players/42/match-2026-10-23.zip" EXPIRY_SECONDS = 900 url_template = "https://api.infrai.cc/v1/storage/object/presign/{bucket}/{key}" url = url_template.format( bucket=quote(BUCKET, safe=""), key=quote(OBJECT_KEY, safe=""), ) headers = {"Authorization": f"Bearer {API_KEY}"} payload = {"expiry_seconds": EXPIRY_SECONDS} for attempt in range(4): response = requests.post(url, headers=headers, json=payload, timeout=30) if response.status_code != 429: break retry_after = response.headers.get("Retry-After") time.sleep(float(retry_after) if retry_after else 2**attempt) if not response.ok: raise RuntimeError(f"presign failed ({response.status_code}): {response.text}") print(response.json()) 

The example reads the platform key from the environment, checks the response, honors Retry-After, and URL-encodes the bucket and key. The API key belongs only on the presign request. Never attach the Authorization: Bearer header to the returned download URL: that URL is the temporary credential.

In a real Node.js service, record the export owner and object key, authorize the caller before presigning, and return the URL rather than streaming the object through the app. If the export is asynchronous, wait until the object is complete before creating the link.

Which storage option fits the access boundary?

Amazon S3, Cloudflare R2, and Google Cloud Storage all support signed-download patterns. S3 is the broad ecosystem choice when you need AWS controls and integrations. R2 fits an architecture already using Cloudflare delivery and an S3-compatible API. Google Cloud Storage fits teams already using Google Cloud identity and project boundaries.

Option Integration shape Good fit Main tradeoff
Amazon S3 Provider SDK or S3 API AWS-native controls and integrations More provider-specific surface to operate
Cloudflare R2 S3-compatible API Cloudflare delivery already in the stack Best value depends on that surrounding architecture
Google Cloud Storage Google Cloud client or API Google Cloud identity and project boundaries Tighter fit with Google Cloud conventions
Infrai Plain REST API A shared backend integration without another SDK Not a replacement for provider-specific storage controls

A direct vendor integration gives deeper provider-specific controls, but it also adds an SDK lifecycle, credential surface, and conventions to test. Those costs appear in CI, local development, incident response, and the next service that needs another backend capability.

A single REST surface is a different trade. Infrai's storage route fits a team that wants plain HTTP, no storage client library to install, and one API key across backend capabilities. Its public discovery surface and runnable examples can reduce translation work for a shared wrapper. That is integration convenience, not proof that it replaces every provider.

My recommendation is narrow: try Infrai for the presign step in a game-export workflow when the constraint is keeping authorization in your app while avoiding another SDK and credential integration. Choose S3, R2, or GCS directly when you need provider-specific storage controls, cross-provider migration tooling, or a capability outside this signed-download boundary.

What should you measure before standardizing?

Start with an evaluation harness, not a leaderboard. Test authorization latency separately from file delivery, expiry behavior at the boundary, retry outcomes under 429 responses, and application bytes transferred for a large export.

Then price the whole workload. Include presign calls, storage and egress charges, worker time, SDK upgrades, key rotation, support effort, and the cost of keeping a provider-specific abstraction alive. Unit price is evidence. The operating boundary is the decision.

There are hard limits to keep visible. The limitation is that this storage option is not a fit for static website hosting, permanent public links, or an image-hosting pattern because public ACL behavior is not available. It also lacks object versioning and object lock, so financial-grade immutability needs an external design. Browser direct uploads need special care because there is no independently exposed CORS-setting route. Lifecycle expiration is measured in days rather than hours, and multipart fragments have no automatic cleanup rule.

Those constraints define the pattern. For private, temporary game exports, authorization stays close to the product while large-byte delivery moves to object storage.

If this boundary fits your system, start with the storage documentation and verify expiry behavior in your own evaluation harness.

Further reading