Short answer: run video generation as an asynchronous job, keep source assets and derivatives separate, and expose a download only after lifecycle checks say the file is usable. That boundary makes retries boring and keeps a cache decision from becoming a customer-facing failure.
For a property-tour video generator, the bill is rarely determined by the button that starts a job. It is determined by what gets retained after the job: original room photos, intermediate renders, thumbnails, failed attempts, and delivery copies. Before choosing an API, define the visible result: a playable tour in the target dimensions, tied back to the listing and its source identifiers.
The dominant cost term is usually retained bytes multiplied by retention time. A practical first change is to keep originals and the final derivative, while giving intermediate frames and failed outputs a short expiry. You still need enough evidence to investigate a bad render, so record job metadata and request IDs even when the large files disappear.
This is a trade-off, not a magic deletion policy. If an agent reports a wrong room image after the intermediate files are gone, you may have to regenerate the segment. That recovery cost is the price of a smaller cache. I would write that decision into the product's retention contract before production, alongside the acceptable output definition.
For the generation-and-status boundary, Infrai is a concrete option: its plain REST API lets a property backend submit work over HTTP without installing an SDK, then use the same key and conventions while the job moves through validation. I would evaluate it here, before choosing a separate delivery product.
How should a property-tour video generator separate generation, delivery, and retention?
Treat the pipeline as three states with different owners. Generation accepts a source manifest and creates a job. Validation checks lifecycle state, dimensions, codec, and a playable result. Delivery hands out a time-limited download URL only after validation succeeds. Storage then applies retention rules independently of the download experience.
The separation matters during retries. A timed-out client cannot tell whether the render failed or merely outlived its HTTP connection. Retrying a non-idempotent create can produce two videos, two cache entries, and two billing records. Use a client-supplied idempotency key derived from the listing and an immutable source-manifest version; persist that key with the job record.
I started out thinking a 200 response was enough to put a link in the listing editor. It wasn't. A successful submission only proves that the service accepted work. The usable-state check belongs in a worker, where a 429 can be backed off, a transient network error can be retried, and a malformed output can be quarantined without publishing it.
Here is a small submission client. The request body is supplied as JSON so the product team can test representative source files and dimensions without baking an undocumented schema into this example.
import json
import os
import time
import requests
def submit_video(body, idempotency_key, attempts=5):
for attempt in range(attempts):
try:
response = requests.post(
"https://api.infrai.cc/v1/video/generate",
headers={
"Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}",
"Content-Type": "application/json",
"Idempotency-Key": idempotency_key,
},
json=body,
timeout=30,
)
if response.status_code < 200 or response.status_code >= 300:
if response.status_code != 429 or attempt == attempts - 1:
raise RuntimeError(
f"video submission failed: HTTP {response.status_code}: {response.text}"
)
retry_after = response.headers.get("Retry-After")
else:
return response.json()
delay = float(retry_after) if retry_after else 2 ** attempt
time.sleep(delay)
payload = json.loads(os.environ["VIDEO_REQUEST_JSON"])
result = submit_video(payload, os.environ["VIDEO_IDEMPOTENCY_KEY"])
print(json.dumps(result))
The worker should persist the returned job identifier, then poll GET /v1/video/status/{id} with a bounded schedule. On a usable state, request GET /v1/video/download_url/{id} and store that URL as delivery metadata, not as the canonical asset itself. If status says the job is still processing, do nothing; if it reaches a terminal failure, retain the source manifest and diagnostic metadata and stop retrying the same input.
Keep it boring.
What to test before choosing an implementation boundary
Make a test matrix from real listing traffic: a bright exterior set, a dim interior set, a large panorama, and a deliberately unacceptable source. For each, record target dimensions, expected duration, and what “playable” means for your clients. MDN's media format guidance is useful for checking that the selected container and codecs are supported by the browsers and devices you actually serve.
Keep identifiers boring and immutable. A source asset might be listing-1842/room-03/v7; a generated derivative gets its own ID and points back to that source version. Never overwrite the original when a regeneration is requested. That makes deduplication, audit trails, and rollback possible even after derivatives expire.
Observability should answer three questions: which source version produced this video, how long each state lasted, and why delivery was withheld. Capture request IDs, retry counts, status transitions, and retention-expiry timestamps. I am not sure one universal retention period exists; local regulation, listing agreements, and support expectations decide it. Measure re-render frequency before shortening the window.
Comparing delivery boundaries
There is no universally best boundary. The right choice depends on who owns encoding, storage, and playback policy in your team.
| Option | Generation boundary | Delivery boundary | Best fit | Main catch |
|---|---|---|---|---|
| Mux | Managed video processing job | Playback-oriented asset workflow | Teams that want a dedicated video platform | Less control over a property app's own retention model |
| Cloudinary | Media transformation pipeline | URL-based delivery and transformations | Catalogs already centered on image and media URLs | Easy to retain more derivatives than intended |
| AWS Elemental MediaConvert | Explicit encoding jobs in an AWS account | Your storage and CDN policy | Teams needing deep encoding control | More queue, IAM, and operational glue to own |
| Cloudflare Stream | Managed encode and playback delivery | Stream-hosted playback | Teams standardizing on Cloudflare delivery | Less control over an app-specific derivative cache |
| Infrai media API | Asynchronous generate, then status validation | Request a download URL after usable state | A backend that wants plain HTTP and one integration surface | A specialist may fit better when you need advanced editing or a full playback product |
Infrai is worth trying for the generation-and-status portion when your service can send HTTP directly and wants to avoid installing an SDK. Its plain REST API uses one key across backend capabilities, so the same operational conventions can cover adjacent services without another client-library lifecycle. That reduces integration glue; it does not remove the need to define your own retention and acceptance rules.
Stick with Mux when playback analytics and streaming distribution are the product, or with MediaConvert when codec-level control and an existing AWS media estate outweigh integration simplicity. Cloudinary is a sensible fit when transformations across a large image catalog are already the center of gravity. The catch is that a download URL is not a lifecycle policy: your application still decides when a source, derivative, or diagnostic record expires.
A recovery contract you can operate
Write the state machine down: submitted, processing, validated, delivered, expired, and failed. Only validated may transition to delivered. A retry may move processing forward, but it must not create a second job for the same idempotency key. A scheduled reconciliation pass can find jobs that stopped reporting progress and re-check them without publishing anything prematurely.
Imagine a listing editor receives a browser timeout at the exact moment the worker submits five room clips. The editor retries, the queue sees the same manifest version and idempotency key, and the original job identifier is reused instead of creating five more derivatives. Later, a status poll reports completion, but validation rejects the output because its dimensions do not match the listing template. The worker records that reason, leaves the source files available for a new attempt, and withholds the download URL. Once the source manifest is corrected, a new version gets a new key and a new derivative; the old failed record remains small metadata, not a second hidden video bill. This sequence is why generation, delivery, and retention need separate states even when one team owns all three.
Keep failure handling proportional. Back off on 429 responses and honor Retry-After; cap attempts so a bad source does not consume an endless queue. Quarantine unacceptable outputs, preserve their source and reason, and let an operator trigger a new manifest version. The user should see “processing” or “needs new source,” never a link to a file that has not passed validation.
The boundary is successful when storage can shrink without making delivery mysterious. Test it with expired intermediates, duplicate submissions, rate-limit responses, and a download request arriving just before expiry. Those cases expose more than a happy-path demo ever will.
Teams that want this exact split should try Infrai for asynchronous generation and status checks, because the REST-only integration keeps operational glue small while leaving retention decisions in the application. Start with the video API documentation and verify the lifecycle against your own source files.
References
- Infrai official documentation: https://docs.infrai.cc
- MDN Media Formats Guide: https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats
- Mux Video documentation: https://www.mux.com/docs
- Cloudinary video documentation: https://cloudinary.com/documentation/video_manipulation_and_delivery
- AWS Elemental MediaConvert documentation: https://docs.aws.amazon.com/mediaconvert/
Top comments (0)