RocketChat/Rocket.Chat · error

Invalid url. It doesn't exist or is not "application/zip".

Error message

Invalid url. It doesn't exist or is not "application/zip".

What it means

Thrown after a successful marketplace download when the response's Content-Type header is not exactly 'application/zip'. The installer expects the v2/apps/:id/download/:version endpoint to return the app package bytes as a zip; anything else (HTML error page, JSON error, redirect body) means the URL did not resolve to a real package and the buffer would be garbage. This is a protocol-level check before Buffer.from(await downloadResponse.arrayBuffer()).

Solutions

  1. Confirm the appId/version pair exists on the marketplace (GET v1/apps/:appId) and reinstall with a published version.
  2. Reproduce the download manually with curl -D - to inspect the actual status and Content-Type returned to the server.
  3. If a security appliance intercepts marketplace.rocket.chat, bypass it or add its CA so the real zip response is preserved.
  4. Retry later if the cloud was serving error bodies during an incident (the metadata check at 165 will usually co-trigger).

Example fix

// before
await installFromMarketplace('invalid-app-id', '9.9.9');
// -> Invalid url. It doesn't exist or is not "application/zip".

// after: resolve an actually published version first
const info = await GET(`v1/apps/${appId}`);
const version = info.versions[0].version; // latest published
await installFromMarketplace(appId, version);
Defensive patterns

Strategy: validation

Validate before calling

// Verify the version is published and downloadable before install
async function isDownloadableZip(appId: string, version: string, token: string): Promise<boolean> {
  const res = await fetch(`https://marketplace.rocket.chat/v2/apps/${appId}/download/${version}?token=${token}`);
  return res.headers.get('content-type') === 'application/zip';
}

Try / catch

try {
  await installFromMarketplace(appId, version);
} catch (e) {
  if (e instanceof Error && e.message.includes('application/zip')) {
    // version/URL is wrong or a proxy rewrote the response — re-check version, do not retry as-is
  }
}

Prevention

When it happens

Trigger: Marketplace returns 200 with text/html (proxy block page, cloud error page) or application/json (error envelope) instead of the zip stream; appVersion does not exist so the download URL serves a JSON 'not found' body with a 200 status; a middlebox rewrites the response. Only the Content-Type header is inspected, so status is not checked here.

Common situations: Passing a version that was never published or was removed from the marketplace; cloud incident serving error pages with 200; transparent proxy (Zscaler/Forcepoint) replacing downloads with an HTML interstitial; app pulled for policy reasons but version still referenced in scripts.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/f0ca77c6719f6382. Report an issue: GitHub.

Appendix: source

Thrown at apps/meteor/ee/server/apps/communication/rest.ts:327

									.catch((cause) => {
										throw new Error('App package download failed', { cause });
									}),
								Apps.getMarketplaceClient()
									.fetch(`v1/apps/${this.bodyParams.appId}?appVersion=${this.bodyParams.version}`, {
										headers: {
											Authorization: `Bearer ${marketplaceToken}`,
											...headers,
										},
										// SECURITY: user needs specific privileges to send this. Bypassing the SSRF check is okay for now.
										ignoreSsrfValidation: true,
									})
									.catch((cause) => {
										throw new Error('App metadata download failed', { cause });
									}),
							]);

							if (downloadResponse.headers.get('content-type') !== 'application/zip') {
								throw new Error('Invalid url. It doesn\'t exist or is not "application/zip".');
							}

							buff = Buffer.from(await downloadResponse.arrayBuffer());
							marketplaceInfo = await marketplaceResponse.json();

							// Note: marketplace responds with an array of the marketplace info on the app, but it is expected
							// to always have one element since we are fetching a specific app version.
							if (!Array.isArray(marketplaceInfo) || marketplaceInfo?.length !== 1) {
								orchestrator.getRocketChatLogger().error({ msg: 'Error getting app information from marketplace', marketplaceInfo });
								throw new Error('Invalid response from the Marketplace');
							}

							permissionsGranted = this.bodyParams.permissionsGranted;
						} catch (err: unknown) {
							let message;

							if (err instanceof Error) {
								orchestrator.getRocketChatLogger().error({ msg: 'Error installing app from marketplace:', err });

View on GitHub (pinned to b2c16d5842)