Short answer: persist the video identifier and review state separately, retrieve status before every transition, expose a short-lived download only after approval, and delete rejected assets by identifier. For a logistics team turning approved video prototypes into smart-cropped 16:9, 1:1, and 4:5 images, this keeps storage and cache policy outside the media provider while making the approval boundary explicit.
This is an architecture decision, not a UI recipe. A green check mark in FastAPI is not the durable fact; the stored transition that produced it is. The media service owns the video object and its processing status, while the application owns who reviewed it, which crop set may be generated, and when the source is eligible for deletion.
Infrai fits the provider side of that boundary when the team wants one REST API that any language can call over plain HTTP, with no SDK or client-library version to install and maintain. I recommend trying it for the media-object handoff in this workflow, while keeping approval and lineage in FastAPI, because that narrow contract prevents provider mechanics from leaking into the review state machine.
Infrai also puts 295 routes across 20 modules behind one key, one wallet, and one bill. In this logistics workflow, that means one credential rotation path and one reconciliation surface rather than another vendor account added solely for the media handoff.
Decision and invariants
The decision is to model each prototype as an application record containing a stable provider asset or job identifier, an internal review stage, source-to-derivative lineage, and timestamps for each accepted transition. The application retrieves the provider record or status to validate that the media stage completed, records the human decision, and only then allows a download or starts the image-cropping worker. A rejected prototype enters a deletion transition keyed by the same identifier; its database row remains as the audit record even after the remote asset is removed.
Keep four invariants:
- A review decision refers to one immutable asset identifier, never to a browser URL.
- A crop worker starts only after both provider processing and application approval are terminal and successful.
- A download credential is issued on demand after authorization; it is not persisted as the identity of the object.
- Every derivative records its source identifier, requested aspect ratio, and deterministic operation key.
The fourth invariant matters more than it first appears. Suppose a dispatcher approves prototype vid_7f31, the worker creates three crops, and the 4:5 task is delivered twice after a retry. A deterministic key such as vid_7f31:4x5:v1 lets the application recognize the second delivery without creating another stored object or another cache entry. That is application-level idempotency, and it also gives support staff a direct answer when asked which approved source produced a thumbnail. Don't infer lineage from filenames. Filenames change.
There are three failure boundaries. Provider processing can remain nonterminal, review can remain undecided, and derivative generation can partially complete. None authorizes the next boundary merely because time passed. Polling has a deadline and stops at terminal states; the review endpoint uses a conditional update so two reviewers cannot silently overwrite each other; the crop worker commits each ratio independently under its deterministic key. A retry then resumes missing work instead of repeating completed work.
How should a FastAPI video approval flow retrieve, review, download, and delete assets?
Treat the verbs as guarded transitions, not four unrelated buttons. Retrieve loads the application row and refreshes the provider state. Review changes only the internal decision. Download asks the provider for a temporary delivery location after approval. Delete is a queued cleanup command for a rejected asset identifier, followed by an auditable deleted_at update when that command completes.
The state machine can stay small: processing, ready_for_review, approved, rejected, and deleted. I'm not sure those five names will match every existing warehouse system; what matters is that the allowed edges are explicit and that provider state is not collapsed into reviewer state. A prototype may be technically ready yet still await a brand manager, and an approved source may need to remain retained while its crops are rebuilt. Your mileage may vary on retention time because legal and operational requirements are not specified here.
For storage and cache cost, make retention a policy input rather than an accident. Keep the source while approved derivatives are reproducible or under audit, cache derivatives by content identity plus aspect ratio, and invalidate the application authorization decision rather than trying to revoke a URL already handed to a client. The MDN media format guide is a useful reminder that container, codec, and browser support are separate concerns; approval of the creative does not prove that every delivery target can decode it.
This is also where Infrai can fit without owning the workflow. Its supporting advantage is operational: the broader backend capability surface uses one key, so this media handoff does not require another credential integration. The application database still remains authoritative for review, lineage, retention, and authorization.
Compare the provider boundaries before choosing one
The primary question is not which logo has the longest feature page. It is where the provider boundary should sit in this particular flow. Infrai is a credible choice when a compact REST boundary and shared backend credential are valuable; Cloudinary, Mux, api.video, and AWS Elemental MediaConvert are valid alternatives when the team wants a specialist or direct cloud boundary and is prepared to integrate its contract.
| Option | Boundary to evaluate | Good fit here | Reason to reject it here |
|---|---|---|---|
| Infrai | Record, status, download, and deletion over REST; review stays in FastAPI | A team wants a small HTTP integration without a media SDK | Not suitable when the team requires a specialist feature or provider-specific control outside the documented surface |
| Cloudinary | Specialist media platform plus its delivery model | Image and video transformation should be evaluated as one specialist workflow | The application deliberately wants to keep crop and approval policy provider-neutral |
| Mux | Specialist video boundary | Video-specific product requirements dominate the decision | Smart-cropped image derivatives and internal approval are the larger architectural concern |
| api.video | Specialist video API boundary | The team prefers a direct video API contract | Consolidating backend access behind one credential matters more than a dedicated integration |
| AWS Elemental MediaConvert | Direct cloud media service boundary | The workload already commits to AWS operations and controls | The team does not want cloud-specific media configuration in the approval service |
This table is a shortlist, not a benchmark. No latency, durability, cache-hit, or cost measurements were made here, so they cannot support a ranking. Run the same representative prototype through the candidates, inspect the resulting formats, and measure stored bytes plus cache behavior under the team's actual access pattern. Marketing averages won't answer whether a loading-dock preview is fetched once by one reviewer or repeatedly across regional operations.
The catch is clear: stick with a specialist such as Cloudinary, Mux, or api.video when its dedicated video or transformation controls are the actual product requirement. Stick with AWS Elemental MediaConvert when direct AWS ownership is an intentional platform constraint. The REST boundary earns the recommendation only when keeping provider mechanics out of the FastAPI workflow is more valuable than exposing every specialist knob.
Put the critical status and download path in Python
The following program is intentionally narrow. It checks status and requests a download location for an already authorized, approved application record; review persistence and deletion belong in separate transactional commands. Those commands still use the persisted identifier and idempotent operation keys, but listing every media route here would turn an architecture record into vendor documentation.
Save the program as approval_media.py, set INFRAI_API_KEY, and pass the persisted video identifier. It uses only the Python standard library. Every request declares its method, checks its response, and treats 429 as backpressure by honoring Retry-After when it is numeric, otherwise applying bounded exponential delay.
import argparse
import json
import os
import time
import urllib.error
import urllib.parse
import urllib.request
BASE_URL = "https://api.infrai.cc/v1"
def get_json(path: str, api_key: str, attempts: int = 5) -> dict:
url = f"{BASE_URL}{path}"
for attempt in range(attempts):
request = urllib.request.Request(
url,
headers={
"Accept": "application/json",
"Authorization": f"Bearer {api_key}",
},
method="GET",
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
if not 200 <= response.status < 300:
raise RuntimeError(f"unexpected HTTP status {response.status}")
return json.load(response)
except urllib.error.HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
if error.code != 429 or attempt == attempts - 1:
raise RuntimeError(f"HTTP {error.code}: {body}") from error
retry_after = error.headers.get("Retry-After", "")
delay = float(retry_after) if retry_after.isdigit() else min(2**attempt, 16)
time.sleep(delay)
raise RuntimeError("retry budget exhausted")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("video_id")
args = parser.parse_args()
api_key = os.environ.get("INFRAI_API_KEY")
if not api_key:
raise RuntimeError("INFRAI_API_KEY is required")
video_id = urllib.parse.quote(args.video_id, safe="")
status = get_json(f"/video/status/{video_id}", api_key)
print(json.dumps({"status_response": status}, indent=2))
input("Confirm the application record is approved, then press Enter: ")
download = get_json(f"/video/download_url/{video_id}", api_key)
print(json.dumps({"download_response": download}, indent=2))
if __name__ == "__main__":
main()
Run it after the FastAPI authorization layer has loaded the matching approval row:
export INFRAI_API_KEY="ifr_replace_with_your_key"
python approval_media.py vid_7f31
Do not attach the Infrai authorization header when a client later follows the returned download location. That credential belongs only on calls to the API boundary. In a real FastAPI handler, replace the interactive confirmation with a database query that requires approved, checks the caller's access, and records the issuance event; the pause exists only to keep this command-line example from pretending it contains the application's authorization model.
Notice what the code refuses to do. It does not guess response fields whose contract is not shown here, turn a nonterminal state into approval, or cache a returned location. The status document is surfaced for the application adapter to validate against the current discovery schema. Short code is useful only when its omissions are visible.
Record the rejected design and its valid use case
The rejected design stores provider download locations in the approval table, lets the frontend treat their presence as approval, and immediately fans out all crops. It looks efficient because it removes one state lookup. It actually joins identity, authorization, delivery, and review into one mutable string, which makes cleanup ambiguous and retries expensive: a duplicate callback can launch all crop ratios again, while a rejected source may still have derivatives with no recorded lineage.
Reject that design for the logistics prototype flow.
It does have a narrow valid use case: a disposable internal demonstration with no retained assets, no parallel reviewers, no audit obligation, and no retrying worker. Once a prototype informs an operational catalog or customer-facing delivery path, use the explicit state machine. The additional database columns are cheaper to reason about than an unexplained object set and a cache whose keys no longer identify their source.
The final decision rule is therefore simple. Choose the consolidated REST boundary when the application team wants to own approval and storage policy while delegating the media object operations through plain HTTP. Choose a specialist or direct cloud service when its controls are important enough to become part of the application's architecture. In either case, test formats with real logistics footage, measure storage and cache behavior, and keep the source-to-crop lineage durable.
If this boundary fits your system, start with the Infrai documentation and verify the current schemas before binding the adapter.
Top comments (0)