remotion-dev/remotion · error · Error

Server returned status code ${res.status} for ${resolvedUrl}

Error message

Server returned status code ${res.status} for ${resolvedUrl} and range ${requestedRange}

What it means

In makeFetchRequest, after the fetch resolves the parser checks res.ok; any non-2xx status throws with the status code, URL, and requested range. This surfaces HTTP-level failures (404, 403, 500, etc.) before the parser tries to interpret bytes.

Source

Thrown at packages/media-parser/src/readers/from-fetch.ts:156

						Range: `bytes=${requestedRange}-`,
					}
				: {
						Range: `bytes=${`${requestedRange[0]}-${requestedRange[1]}`}`,
					};

	const res = await fetch(resolvedUrl, {
		headers,
		signal: ownController.signal,
		cache,
	});

	const contentRange = res.headers.get('content-range');
	const parsedContentRange = contentRange
		? parseContentRange(contentRange)
		: null;

	if (!res.ok) {
		throw new Error(
			`Server returned status code ${res.status} for ${resolvedUrl} and range ${requestedRange}`,
		);
	}

	const {supportsContentRange} = validateContentRangeAndDetectIfSupported({
		requestedRange,
		parsedContentRange,
		statusCode: res.status,
	});

	if (controller) {
		controller._internals.signal.addEventListener(
			'abort',
			() => {
				ownController.abort(new MediaParserAbortError('Aborted by user'));
			},
			{once: true},
		);

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Open the URL in a browser / curl `-I` to see the exact status and fix the root cause (404 -> correct path, 403 -> refresh credentials).
  2. For presigned URLs, regenerate with a longer expiry before parsing.
  3. Ensure any required auth/cookie headers are present and that CORS allows your origin.
  4. Retry with backoff for transient 5xx/429, and surface a clear error to the end user.

Example fix

// before
await parseMedia({src: presignedUrl, fields: {durationInSeconds: true}});

// after: refresh presigned URL and handle status
const url = await getFreshPresignedUrl(key); // TTL-aware
try {
  await parseMedia({src: url, fields: {durationInSeconds: true}});
} catch (e) {
  if (/status code 40[13]/.test(e.message)) throw new Error('Media URL expired or forbidden');
  throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

// HEAD-check the URL before parsing
const head = await fetch(url, {method: 'HEAD'});
if (!head.ok) throw new Error(`Media URL returned ${head.status}`);

Try / catch

async function withRetry<T>(fn: () => Promise<T>, retries = 3): Promise<T> {
  for (let i = 0; i < retries; i++) {
    try { return await fn(); }
    catch (e) {
      const m = e instanceof Error ? e.message : '';
      if (/status code 5\d\d|429/.test(m) && i < retries - 1) {
        await new Promise(r => setTimeout(r, 2 ** i * 500)); continue;
      }
      throw e;
    }
  }
  throw new Error('unreachable');
}
await withRetry(() => parseMedia({src: url, fields: {durationInSeconds: true}}));

Prevention

When it happens

Trigger: The media URL returns a non-2xx HTTP status: 404 (wrong URL / asset removed), 403 (auth/permissions, expired presigned URL), 401 (auth required), 5xx (server error), or 429 (rate limited).

Common situations: Expired S3 presigned URLs, wrong/deleted asset paths, missing auth headers, CORS-preFlight failures surfacing as opaque errors, CDN origin errors, rate limiting, or hotlink protection blocking the request.

Related errors


AI-assisted analysis of remotion-dev/remotion@78fe4bb3fd (2026-08-12). Data as JSON: /api/errors/a5aee8994892d045. Report an issue: GitHub.