twentyhq/twenty · error · Error

download failed with status ${response.status}

Error message

download failed with status ${response.status}

What it means

During media import, the call-recorder downloads a recording artifact (video/audio) from Recall.ai over HTTP. After streaming-log of the response, it checks `response.ok`; if Recall returned a non-2xx status the code cancels the response body (to avoid leaking the connection) and throws this error. The status code is interpolated, so the message tells you exactly what Recall returned (404, 403, 502, etc.).

Source

Thrown at packages/twenty-apps/public/call-recorder/src/logic-functions/flows/import-call-recording-media.util.ts:207

  const response = await fetch(url, {
    signal: AbortSignal.timeout(MEDIA_DOWNLOAD_TIMEOUT_MS),
  });
  const contentLengthBytes = parseContentLengthBytes(
    response.headers.get('content-length'),
  );

  console.log(
    `[call-recorder] media-import phase=artifact-download-response callRecordingId=${callRecordingId} fileName=${fileName} responseStatus=${response.status} contentLengthBytes=${contentLengthBytes ?? 'unknown'} ${formatMemoryUsageForLog()}`,
  );

  if (!response.ok) {
    await cancelMediaDownloadBody({
      callRecordingId,
      fileName,
      body: response.body,
    });

    throw new Error(`download failed with status ${response.status}`);
  }

  if (isUndefined(contentLengthBytes)) {
    await cancelMediaDownloadBody({
      callRecordingId,
      fileName,
      body: response.body,
    });

    throw new Error('download response is missing content-length');
  }

  if (contentLengthBytes > maxMediaFileSizeBytes) {
    await cancelMediaDownloadBody({
      callRecordingId,
      fileName,
      body: response.body,
    });

View on GitHub (pinned to 1f5dd2bbd2)

Solutions

  1. Read the interpolated status: 404/410 → the artifact URL is gone, re-fetch the recording from Recall to get a fresh media URL before downloading; 403 → check the Recall API key / token scope; 5xx → retry after backoff.
  2. Re-fetch the Recall recording (`getRecallRecording`) immediately before download to obtain a fresh presigned media URL rather than reusing a cached one.
  3. Confirm the call recording's `externalRecordingId` still maps to a Recall recording that has finished processing.
  4. If the artifact is permanently unavailable, mark the call recording failed with the appropriate failure reason instead of retrying indefinitely.

Example fix

// before
if (!response.ok) {
  await cancelMediaDownloadBody({ callRecordingId, fileName, body: response.body });
  throw new Error(`download failed with status ${response.status}`);
}

// after — retry once with a freshly fetched media URL on 404/410
if (!response.ok) {
  await cancelMediaDownloadBody({ callRecordingId, fileName, body: response.body });
  if (response.status === 404 || response.status === 410) {
    throw new Error(
      `download failed with status ${response.status} (media URL likely expired; re-fetch recording ${externalRecordingId})`,
    );
  }
  throw new Error(`download failed with status ${response.status}`);
}
Defensive patterns

Strategy: retry

Validate before calling

// Fetch a fresh recording right before download so the media URL is not stale.
const recording = await getRecallRecording({ externalRecordingId });
if (!recording.ok) {
  throw new Error(`Cannot download media: Recall recording fetch failed (${recording.errorMessage})`);
}
const mediaUrls = extractRecallMediaUrls(recording.recording);
if (mediaUrls.length === 0) {
  throw new Error('Recall recording has no media artifacts to download');
}

Try / catch

async function downloadWithRetry(url: string, attempts: number): Promise<Response> {
  let lastStatus = 0;
  for (let i = 0; i < attempts; i++) {
    const res = await fetch(url, { signal: AbortSignal.timeout(MEDIA_DOWNLOAD_TIMEOUT_MS) });
    if (res.ok) return res;
    lastStatus = res.status;
    await res.body?.cancel().catch(() => {});
    // Retry only transient statuses; do not retry 404/410.
    if (res.status < 500 && res.status !== 429) break;
    await new Promise((r) => setTimeout(r, 2 ** i * 500));
  }
  throw new Error(`download failed with status ${lastStatus}`);
}

Prevention

When it happens

Trigger: Recall.ai returns 404 (the media URL expired or the recording artifact was deleted), 403 (auth/token problem on the media host), 410 (artifact retired), or 5xx (Recall upstream outage). The download URL comes from `getRecallRecording` / `extractRecallMediaUrls`, so a stale or presigned-expired URL is the most common trigger.

Common situations: Webhooks processed long after the recording completed (presigned media URLs expired), a Recall migration that changed media host paths, regional Recall outages, or an expired/rotated Recall API credential producing 403s on the media endpoint.

Related errors


AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12). Data as JSON: /api/errors/4512d34f64bb717f. Report an issue: GitHub.