firecrawl/firecrawl · error · ZscalerError
auth
auth
Error message
Zscaler rejected the OAuth client credentials
What it means
When the Zscaler token endpoint responds with HTTP 400 or 401, fetchAccessToken throws ZscalerError of kind 'auth' with the status attached. This specifically means the OAuth client credentials (clientId/clientSecret) were rejected — wrong, expired, or revoked. The body is drained to free the connection.
Source
Thrown at apps/api/src/lib/threat-protection/providers/zscaler/client.ts:189
try {
response = await fetch(tokenUrl(credentials), {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: body.toString(),
signal,
// undici extension: route through the partner egress proxy.
dispatcher: getDispatcher(),
} as RequestInit);
} catch (error) {
throw new ZscalerError(
"api",
`Failed to reach the Zscaler token endpoint: ${error instanceof Error ? error.message : String(error)}`,
);
}
if (response.status === 400 || response.status === 401) {
drainBody(response);
throw new ZscalerError(
"auth",
"Zscaler rejected the OAuth client credentials",
response.status,
);
}
if (response.status === 429) {
drainBody(response);
throw new ZscalerError(
"rate-limit",
"Zscaler token endpoint rate limit hit",
response.status,
retryAfterMs(response),
);
}
if (!response.ok) {
drainBody(response);
throw new ZscalerError(
"api",View on GitHub (pinned to 656bffcc28)
Solutions
- Re-issue or re-copy the clientId and clientSecret from the Zscaler/Zidentity console and save them.
- Confirm the credentials match the same vanityDomain/cloud being used.
- Use Zscaler's two-active-secrets support: add the new secret first, verify, then remove the old one for zero-downtime rotation.
- Trim whitespace from credential values before storing them.
Example fix
// before
// credentials.clientSecret = " abcdef==" // leading space
// after
const cleanSecret = credentials.clientSecret.trim();
const token = await fetchAccessToken({ ...credentials, clientSecret: cleanSecret }, signal); Defensive patterns
Strategy: try-catch
Validate before calling
function looksLikeValidZscalerCreds(c: ZscalerCredentials): boolean {
return !!c.clientId && c.clientId.trim() === c.clientId
&& !!c.clientSecret && c.clientSecret.trim() === c.clientSecret
&& c.clientSecret.length >= 8;
}
if (!looksLikeValidZscalerCreds(credentials)) {
throw new Error("Zscaler credentials look malformed or contain whitespace");
} Type guard
function isZscalerAuthError(e: unknown): e is ZscalerError {
return e instanceof ZscalerError && e.kind === "auth";
} Try / catch
try {
token = await fetchAccessToken(credentials, signal);
} catch (e) {
if (e instanceof ZscalerError && e.kind === "auth") {
// do NOT retry with the same credentials — they are wrong/revoked
throw new Error("Zscaler rejected the OAuth client credentials. Re-issue and update them.");
}
throw e;
} Prevention
- Do not retry auth errors — re-fetching with the same credentials will keep failing.
- Trim and validate credentials at entry to avoid whitespace/typo failures.
- Use Zscaler's two-active-secrets support for zero-downtime rotation.
When it happens
Trigger: The stored ZscalerCredentials have an incorrect clientId or clientSecret, the secret was rotated/revoked in Zscaler, the client is disabled, or the credentials were copied with whitespace/typos. Any 400/401 from the token endpoint maps to this auth error.
Common situations: Secret rotation done in Zscaler but not yet saved in this system; copy-paste error introducing whitespace; wrong Zidentity/vanity cloud; client disabled by a Zscaler admin.
Related errors
- api
- rate-limit
- zscaler.clientSecret is required when configuring a Zscaler
- Unauthenticated Playground calls are temporarily disabled du
- unsafe_domain_blocked
AI-assisted analysis of firecrawl/firecrawl@656bffcc28 (2026-08-12).
Data as JSON: /api/errors/5c09452bbf59a3bf.
Report an issue: GitHub.