{"record":{"id":"9b1c3ad95736bb09","repo":"can1357/oh-my-pi","slug":"z-ai-business-login-returned-no-access-token","errorCode":null,"errorMessage":"Z.ai business login returned no access token","messagePattern":"Z\\.ai business login returned no access token","errorType":"exception","errorClass":"AIError.OAuthError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/registry/oauth/zai.ts","lineNumber":135,"sourceCode":"}\n\nfunction trimmedString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" && value.trim().length > 0 ? value.trim() : undefined;\n}\n\n/**\n * Exchange the short-lived OAuth access token for a durable biz token via\n * ZCode's business-login endpoint. The biz APIs reject the raw OAuth token;\n * they require this token.\n */\nasync function businessLogin(oauthAccessToken: string, fetchImpl: FetchImpl): Promise<string> {\n\tconst data = unwrapEnvelope(\n\t\tawait postJson(BUSINESS_LOGIN_URL, { token: oauthAccessToken }, {}, fetchImpl),\n\t\t\"business login\",\n\t) as { access_token?: unknown; accessToken?: unknown } | undefined;\n\tconst bizToken = trimmedString(data?.access_token) ?? trimmedString(data?.accessToken);\n\tif (!bizToken) {\n\t\tthrow new AIError.OAuthError(\"Z.ai business login returned no access token\", {\n\t\t\tkind: \"token-exchange\",\n\t\t\tprovider: \"zai\",\n\t\t});\n\t}\n\treturn bizToken;\n}\n\ninterface ZaiProject {\n\tprojectId?: unknown;\n\tisDefault?: unknown;\n}\ninterface ZaiOrganization {\n\torganizationId?: unknown;\n\tisDefault?: unknown;\n\tprojects?: ZaiProject[];\n}\n\n/**","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/registry/oauth/zai.ts#L117-L153","documentation":"businessLogin exchanges the user's OAuth access token for a business token via the Z.ai business login endpoint. If the unwrapped envelope contains neither access_token nor accessToken (or they are empty/non-string), the flow throws OAuthError (kind=token-exchange, provider=zai). The API succeeded at HTTP level but the response shape lacked the expected credential.","triggerScenarios":"Z.ai returns a successful envelope whose data omits access_token — e.g. account not provisioned for business API, consent missing, or Z.ai changed the response field name/version.","commonSituations":"New Z.ai account without business API enablement; API contract drift on Z.ai side; token valid but scoped to an account type that cannot mint business tokens.","solutions":["Confirm the Z.ai account has business/API access enabled and complete any required consent in the Z.ai console.","Re-run OAuth login to get a fresh token and retry — a partially valid token can yield a tokenless response.","Log the raw business login response (via fetch interception) to check for renamed fields; upgrade the library if Z.ai changed its schema.","Catch AIError.OAuthError and surface a clear message directing the user to enable Z.ai business API."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"if (!oauthAccessToken || oauthAccessToken.length < 20) throw new Error(\"OAuth access token missing/short; re-run Z.ai login first\");","typeGuard":"function hasBizToken(d: unknown): d is { access_token: string } | { accessToken: string } {\n  if (!d || typeof d !== \"object\") return false;\n  const o = d as Record<string, unknown>;\n  return typeof o.access_token === \"string\" && o.access_token.length > 0 || typeof o.accessToken === \"string\" && o.accessToken.length > 0;\n}","tryCatchPattern":"try { const biz = await businessLogin(token, fetch); }\ncatch (e) { if (e instanceof AIError.OAuthError) { logger.error(\"Z.ai business login returned no token; check account business-API enablement\"); return null; } throw e; }","preventionTips":["Enable business API access for the Z.ai account before login","Re-authenticate when exchanges fail repeatedly","Pin/monitor the library version against Z.ai API schema changes"],"tags":["zai","oauth","token-exchange","missing-field","api-contract"],"backgroundTag":"missing-credential-in-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}