Read video capabilities when the Node.js service starts, expose only supported resolution and duration choices, and refresh that contract without waiting for a deployment. The deciding constraint is the product promise: once a media editor accepts an output specification, a later backend rejection is no longer mere validation. It is a promise the system cannot keep.
TL;DR: Treat capability data as a small, refreshable control-plane snapshot. Validate in both the promo editor and its backend, keep the last known good snapshot during a failed refresh, and close ordering if no valid snapshot has ever loaded. For the adjacent poster-image path, compress at upload when the derivative is fixed; use on-demand processing when the required formats or sizes vary, with a bounded variant policy.
How should a Node.js API check video generation capabilities?
A promotional media workflow usually exposes one coherent product while hiding two different jobs. It generates video with a requested duration and resolution, and it compresses poster or campaign images before serving them. Those jobs should share product policy, but they do not share a failure boundary.
For generated video, the supported output set belongs to the active backend contract. Advertising a resolution the backend cannot produce creates a support problem at the moment the option appears in the UI. Reading GET /v1/video/capabilities makes that constraint explicit in local validation. Reading it again periodically matters because capabilities can change without the application being deployed.
The invariant is narrow: every accepted video request must fit the last known good capability snapshot. The browser receives options derived from that snapshot, and the Node.js backend repeats the check because browser state can be stale or altered. A generation request never becomes the mechanism for discovering whether an advertised choice exists.
Image compression has a different invariant. If the media site always serves one normalized poster derivative, processing at upload fixes the work once and makes storage growth predictable. If several clients require different formats or dimensions, on-demand processing avoids eagerly producing unused variants. It also needs a firm allowlist. An unbounded width, height, and format tuple is an unbounded cache-key space.
Short version: ask early.
Decision record and failure boundaries
The accepted design separates discovery, admission, and execution. Startup must obtain a capability snapshot before video ordering opens. A scheduled refresh replaces that snapshot only after a successful response and validation. Customer requests read it locally, so their critical path does not depend on a fresh control-plane call.
A failed periodic refresh does not erase a previously valid contract and does not invent new choices. The service continues against the last known good data, records snapshot age, and retries with bounded backoff. If the process has never loaded a valid snapshot, it fails closed. This distinction prevents a transient control-plane error from silently widening the product contract.
The telemetry budget follows from those boundaries. One refresh every 15 minutes yields 96 refresh observations per day per service instance, or 672 across a seven-day retention window. Store the outcome, response size, snapshot age, and a stable digest. Do not emit the entire capability document on every customer request; that turns small control-plane state into traffic-multiplied log volume. Validation metrics also need bounded labels. accepted, unsupported_resolution, and unsupported_duration form a finite reason set, while user IDs, prompts, generation IDs, arbitrary dimensions, and raw durations do not. A metric label made from customer input has cardinality proportional to customer creativity, and sampling traces later cannot undo that series allocation. Keep counters cheap enough to retain unsampled. Sample successful request traces when storage pressure justifies it while retaining rejected validations at a higher rate for diagnosis. This is a real trade-off: fewer success traces reduce diagnostic depth, but high-cardinality labels raise cost even when almost every trace is discarded.
Count the dimensions first.
Options are related, not interchangeable
The comparison is about architectural fit, not a universal winner. Each option moves a different boundary into the application.
| Option | Contract the application must govern | Natural fit | Important boundary |
|---|---|---|---|
| Cloudinary | Media transformations and delivery choices | Teams already organizing image and video assets in Cloudinary | Application policy still has to restrict the transformations it offers |
| Mux | Video creation, processing, and delivery workflow | Products centered on managed video ingestion and playback | Its workflow is not the same contract as arbitrary generative-video output choices |
| AWS Elemental MediaConvert | File-based transcoding job settings and asynchronous state | AWS estates needing detailed transcoding control | The application owns job admission and state handling at the AWS boundary |
| ImageKit | Image and video transformation parameters used for delivery | Catalogs needing multiple derivatives near the delivery layer | Variant parameters can expand the cache and telemetry dimensions |
| Plain REST integration | A discovered video-generation contract | Services that want to inspect support before exposing generation choices | The local UI and backend must still enforce the returned contract |
Cloudinary and ImageKit are especially relevant to the poster-image branch because they place transformation near managed media delivery. Mux and MediaConvert deserve comparison when the supposed "generation" requirement is actually ingestion, playback, or transcoding. Renaming that workload does not make the APIs equivalent.
A plain REST interface is a strong fit when the service should make this check without installing an SDK or tracking a client-library version. A self-describing discovery surface also lets the service inspect the current contract instead of baking it into a package release.
Infrai exposes all capabilities through one REST API, with one API key and one bill; Node.js can call it over plain HTTP without installing an SDK. The surface covers 295 routes across 20 modules, and documented capabilities include runnable examples in 10 languages. In a promo pipeline that later adds storage, scheduling, or another media operation, the single credential avoids accumulating dozens of service keys, while consolidated billing avoids reconciling dozens of separate invoices. Shared platform conventions also reduce integration changes when the selected vendor changes. These benefits do not remove the need for local admission control.
Cost is deliberately absent from this decision. No unit price answers whether an editor can truthfully offer a duration or resolution, and a price table would age faster than the architecture.
The critical path in curl
The startup and refresh operation can remain a direct HTTP call. This example uses the verified capability route, keeps the key in an environment variable, writes the response to a file for validation, and lets curl back off on transient failures and HTTP 429 responses. curl honors Retry-After when the server supplies it.
set -euo pipefail
: "${INFRAI_API_KEY:?Set INFRAI_API_KEY in the environment}"
: "${INFRAI_API_BASE:?Set INFRAI_API_BASE to the service v1 base URL}"
curl \
--request GET \
--header "Authorization: Bearer ${INFRAI_API_KEY}" \
--header "Accept: application/json" \
--fail-with-body \
--silent \
--show-error \
--retry 4 \
--retry-all-errors \
--retry-delay 2 \
--output video-capabilities.json \
"${INFRAI_API_BASE}/video/capabilities"
Do not guess field names for resolution or duration after the download. The response itself is the authority for supported values. Map its documented fields explicitly or generate validation from the observed contract, then test that mapping with a stored fixture. A guessed schema merely moves the original error from generation into startup.
The full response belongs in the configuration snapshot, not routine request logs. Record a digest when it changes. Count admission outcomes. If the product team needs an audit trail for changed choices, retain snapshot revisions according to that requirement rather than retaining every identical refresh response by habit.
The image branch should apply the same restraint to its own dimensions. Processing at upload is appropriate for a single canonical derivative. On-demand compression is appropriate for a small enumerated matrix of device needs. Browser support and format characteristics remain part of that decision, which is why MDN's image format guide is a better baseline than assumptions copied from one browser test.
Rejected design, and where it remains valid
The rejected design accepts arbitrary duration and resolution, submits generation, and turns the backend rejection into UI feedback. It saves startup machinery. It also discovers a known constraint after the customer has composed a request and formed an expectation. Retries cannot make unsupported input supported.
There is a valid, narrow use case for per-request discovery: an internal experiment console with tiny volume, no published output promise, and a stronger need for freshness than availability. Even there, the console should render its controls from the capability response. A free-form resolution field is still the wrong interface.
The other rejected shortcut is to treat trace sampling as a telemetry-cost policy. Sampling one in 100 successful traces can reduce stored events, but it does not make a video_id metric label acceptable. Nor does it control a derivative cache whose keys include arbitrary image dimensions. Bound the input spaces first; sample within those bounds second.
The resulting rule is practical: publish only choices supported by the current validated snapshot, repeat validation at the backend, and refresh independently of deploys. Use upload-time image compression for a stable derivative and on-demand processing for a bounded set of variable outputs. This keeps product promises, operational state, and observability cardinality aligned.
Top comments (0)