immich-app/immich · error · Error
await response.text()
Error message
await response.text()
What it means
uploadFile performs the asset-media PUT via fetch-like ofetch; when the Immich server responds with any status other than 200/201, the raw body text is read and thrown as the Error message. The developer-facing message is therefore whatever the server returned (e.g. an HTML error page, a JSON problem body, or an empty string), not a structured error.
Solutions
- Read the thrown message: it contains the server's response body, which usually names the real cause (401 => fix IMMICH_API_KEY, 413 => raise client_max_body_size).
- For 413 behind nginx, set client_max_body_size (e.g. 50000M) and restart the proxy.
- Verify the server URL and API key with `immich server-info`.
- Retry large uploads on a stable connection; check Immich server logs for the corresponding request error.
Defensive patterns
Strategy: try-catch
Validate before calling
const info = await getServerInfo(); // verifies API key + URL before big uploads
if (!info) throw new Error('check IMMICH_INSTANCE_URL / IMMICH_API_KEY'); Try / catch
try {
await uploadFile(filepath, stats, options);
} catch (err) {
const msg = String(err);
if (msg.includes('401') || msg.toLowerCase().includes('unauthorized')) fixApiKeyAndRetry();
else if (/413|too large/i.test(msg)) console.error('raise client_max_body_size on the reverse proxy');
else throw err;
} Prevention
- Call server-info first to validate credentials and URL
- Configure reverse proxy client_max_body_size for large videos
- Retry uploads with backoff for transient 5xx
When it happens
Trigger: The server returns 400 (bad upload payload/size limits), 401/403 (bad or missing API key), 413 (file too large for the reverse proxy, e.g. nginx client_max_body_size), or 5xx (server crash/storage full) during `immich upload`.
Common situations: Expired or wrong IMMICH_API_KEY, reverse proxy (nginx/Traefik/Cloudflare) rejecting large video files with 413, Immich server restarting mid-upload, or uploading to the wrong base URL hitting an auth/HTML page.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- errors.unable_to_upload_file
- EOFException
- Failed to fetch activation key
- Machine learning request to
- Server is not reachable
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/139e8077e726e1db.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/commands/asset.ts:441
try {
const stats = await stat(sidecarPath);
const sidecarData = new UploadFile(sidecarPath, stats.size);
formData.append('sidecarData', sidecarData);
} catch {
// noop
}
}
const response = await fetch(`${baseUrl}/assets`, {
method: 'post',
redirect: 'error',
headers: headers as Record<string, string>,
body: formData,
// eslint-disable-next-line unicorn/no-null
window: null,
});
if (response.status !== 200 && response.status !== 201) {
throw new Error(await response.text());
}
return response.json() as Promise<AssetMediaResponseDto>;
};
export const findSidecar = (filepath: string): string | undefined => {
const assetPath = path.parse(filepath);
const noExtension = path.join(assetPath.dir, assetPath.name);
// XMP sidecars can come in two filename formats. For a photo named photo.ext, the filenames are photo.ext.xmp and photo.xmp
for (const sidecarPath of [`${noExtension}.xmp`, `${filepath}.xmp`]) {
if (existsSync(sidecarPath)) {
return sidecarPath;
}
}
};
export const deleteFiles = async (uploaded: Asset[], duplicates: Asset[], options: UploadOptionsDto): Promise<void> => {View on GitHub (pinned to e55ac299a4)