{"record":{"id":"5c09452bbf59a3bf","repo":"firecrawl/firecrawl","slug":"auth","errorCode":"auth","errorMessage":"Zscaler rejected the OAuth client credentials","messagePattern":"Zscaler rejected the OAuth client credentials","errorType":"exception","errorClass":"ZscalerError","httpStatus":null,"severity":"error","filePath":"apps/api/src/lib/threat-protection/providers/zscaler/client.ts","lineNumber":189,"sourceCode":"  try {\n    response = await fetch(tokenUrl(credentials), {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n      body: body.toString(),\n      signal,\n      // undici extension: route through the partner egress proxy.\n      dispatcher: getDispatcher(),\n    } as RequestInit);\n  } catch (error) {\n    throw new ZscalerError(\n      \"api\",\n      `Failed to reach the Zscaler token endpoint: ${error instanceof Error ? error.message : String(error)}`,\n    );\n  }\n\n  if (response.status === 400 || response.status === 401) {\n    drainBody(response);\n    throw new ZscalerError(\n      \"auth\",\n      \"Zscaler rejected the OAuth client credentials\",\n      response.status,\n    );\n  }\n  if (response.status === 429) {\n    drainBody(response);\n    throw new ZscalerError(\n      \"rate-limit\",\n      \"Zscaler token endpoint rate limit hit\",\n      response.status,\n      retryAfterMs(response),\n    );\n  }\n  if (!response.ok) {\n    drainBody(response);\n    throw new ZscalerError(\n      \"api\",","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/firecrawl/firecrawl/blob/656bffcc2883f1af5befe38766b1ff5f0469993a/apps/api/src/lib/threat-protection/providers/zscaler/client.ts#L171-L207","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n// credentials.clientSecret = \"  abcdef==\"  // leading space\n\n// after\nconst cleanSecret = credentials.clientSecret.trim();\nconst token = await fetchAccessToken({ ...credentials, clientSecret: cleanSecret }, signal);","handlingStrategy":"try-catch","validationCode":"function looksLikeValidZscalerCreds(c: ZscalerCredentials): boolean {\n  return !!c.clientId && c.clientId.trim() === c.clientId\n    && !!c.clientSecret && c.clientSecret.trim() === c.clientSecret\n    && c.clientSecret.length >= 8;\n}\n\nif (!looksLikeValidZscalerCreds(credentials)) {\n  throw new Error(\"Zscaler credentials look malformed or contain whitespace\");\n}","typeGuard":"function isZscalerAuthError(e: unknown): e is ZscalerError {\n  return e instanceof ZscalerError && e.kind === \"auth\";\n}","tryCatchPattern":"try {\n  token = await fetchAccessToken(credentials, signal);\n} catch (e) {\n  if (e instanceof ZscalerError && e.kind === \"auth\") {\n    // do NOT retry with the same credentials — they are wrong/revoked\n    throw new Error(\"Zscaler rejected the OAuth client credentials. Re-issue and update them.\");\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["zscaler","threat-protection","auth","oauth","credentials"],"analyzedSha":"656bffcc2883f1af5befe38766b1ff5f0469993a","analyzedAt":"2026-08-12T01:18:00.488Z","schemaVersion":2},"datasetVersion":"2026-08-12T06:17:24.410Z"}