A media URL can download successfully in a command-line client while a browser refuses to expose its response to JavaScript. Check which server answered, whether access is still valid, and whether that response permits your application's origin. Recreating the media job will not repair a missing CORS response header.
This guide targets applications using fetch() to inspect or consume a delivered file. A successful browser navigation, a successful server download, and a successful JavaScript fetch are different observations. Record which one failed before changing the workflow.
Identify the request that failed
Separate the request that creates or checks a media job from the request that retrieves the resulting artifact. They can go to different hosts with different access policies. Tornado's public job-status reference documents job status and delivery information; it does not establish the CORS policy of every storage destination you might use.
Keep your Tornado API key on your backend. Do not copy an API authentication header onto a storage download merely because both requests belong to the same workflow. For a signed delivery URL, preserve the signature and follow that destination's documented authentication requirements.
Open your application's Network panel and find the failing request. Record its method, your page's origin, the destination host, redirect behavior, and the browser's stated reason. Avoid sharing full signed URLs in screenshots or logs; use the media logging guide to retain useful diagnostics without access tokens.
Separate access failure from browser permission
An origin consists of scheme, host, and port. A page on a different origin needs an appropriate CORS response to read a resource through fetch(). The MDN CORS guide explains this browser-enforced exchange.
Inspect the response that actually failed, including any preflight request. A storage error can also lack CORS headers, so a console CORS message does not establish that the underlying object request would otherwise succeed. Investigate an expired URL or denied object access before changing browser policy. Our expired media URL guide covers that branch of the diagnosis.
Do not use a successful command-line download as proof that browser access is configured. It is evidence about that client's request, not about what the browser permits JavaScript to read.
Keep a diagnostic fetch small and explicit
The following helper checks whether response headers become available and cancels the body stream. Call it from your application's origin with a URL you are authorized to inspect. Use a small test artifact: cancellation does not guarantee that zero body bytes were transferred.
async function inspectMediaResponse(url) {
let response;
try {
response = await fetch(url, {
method: "GET",
mode: "cors",
credentials: "omit",
signal: AbortSignal.timeout(10000),
});
return {
status: response.status,
ok: response.ok,
contentType: response.headers.get("content-type"),
etag: response.headers.get("etag"),
};
} catch (error) {
return {
failure: error?.name === "TimeoutError"
? "timeout"
: "fetch-rejected",
};
} finally {
if (response?.body) {
try { await response.body.cancel(); } catch { /* diagnostic only */ }
}
}
}
The helper deliberately reports a generic rejection. A rejected fetch alone cannot distinguish CORS from DNS, TLS, connectivity, or another browser restriction. Use the Network and Console panels to narrow the cause. An HTTP error visible to JavaScript instead appears through status and ok.
This credential-free example fits URLs that do not require browser cookies or HTTP authentication. It is not a recipe for a cookie-authenticated media endpoint. It also needs a browser supporting AbortSignal.timeout; the timeout reference describes its support and active-time behavior. The limit is not a strict wall-clock guarantee across suspended pages.
The helper was checked with simulated successful, HTTP-error, rejected, timeout, and cancellation responses. Those checks validate its reporting logic, not a production storage policy or actual media playback.
Check preflight and exposed headers separately
An unnecessary custom request header can introduce an OPTIONS preflight. If the browser sends one, inspect the requested method and header names, then compare them with what your server allows. Removing an accidental header is different from removing authentication that the resource genuinely requires.
Once the body is readable, a specific response header can still be hidden from JavaScript. For example, reading ETag across origins generally requires the server to expose it. A null value from the helper can mean either an absent header or one that is not exposed. Check the actual response before deciding which. The Fetch standard's CORS protocol defines the permission and header-exposure rules.
For an allowlisted, credential-free application origin, the relevant response might include:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Expose-Headers: ETag
Vary: Origin
These are illustrative response headers, not commands to paste into client code. Replace the example origin with an explicitly allowed origin. Vary: Origin matters when responses vary by request origin; review the CDN's caching behavior too. Apply the policy on the server serving the artifact, including relevant error responses. Do not reflect arbitrary origins without an allowlist.
Avoid fixes that hide the problem
Changing to mode: "no-cors" does not grant readable cross-origin access. It yields an opaque response whose body and headers are unavailable to JavaScript, as the request-mode reference explains. That cannot satisfy an application that needs to inspect or process the returned media.
Likewise, adding Access-Control-Allow-Origin to the request does not authorize the response. A response policy belongs on the serving endpoint. If you do not control that endpoint, choose a supported delivery destination you control or design an authenticated backend handoff with explicit resource authorization. Avoid turning that backend into an unrestricted URL proxy.
After a policy change, retry from the real application origin and verify the intended success and failure paths. Browser-readable bytes still need format and codec checks before playback; use the container and codec guide for that next step. Native video and subtitle loading have their own setup, covered by the WebVTT player guide.
Build your first Tornado media workflow, confirm delivery, then validate browser access on the exact destination your application will use. Keep those outcomes separate so a browser policy problem does not trigger another media acquisition job.